mirror of
https://github.com/jayofelony/pwnagotchi.git
synced 2025-07-01 10:27:27 -04:00
Update everyting!
Signed-off-by: jayofelony <oudshoorn.jeroen@gmail.com>
This commit is contained in:
65
Makefile
Normal file
65
Makefile
Normal file
@ -0,0 +1,65 @@
|
||||
PACKER_VERSION := 1.10.1
|
||||
PWN_HOSTNAME := pwnagotchi
|
||||
PWN_VERSION := $(shell cut -d"'" -f2 < pwnagotchi/_version.py)
|
||||
|
||||
MACHINE_TYPE := $(shell uname -m)
|
||||
ifneq (,$(filter x86_64,$(MACHINE_TYPE)))
|
||||
GOARCH := amd64
|
||||
else ifneq (,$(filter i686,$(MACHINE_TYPE)))
|
||||
GOARCH := 386
|
||||
else ifneq (,$(filter arm64% aarch64%,$(MACHINE_TYPE)))
|
||||
GOARCH := arm64
|
||||
else ifneq (,$(filter arm%,$(MACHINE_TYPE)))
|
||||
GOARCH := arm
|
||||
else
|
||||
GOARCH := amd64
|
||||
$(warning Unable to detect CPU arch from machine type $(MACHINE_TYPE), assuming $(GOARCH))
|
||||
endif
|
||||
|
||||
# The Ansible part of the build can inadvertently change the active hostname of
|
||||
# the build machine while updating the permanent hostname of the build image.
|
||||
# If the unshare command is available, use it to create a separate namespace
|
||||
# so hostname changes won't affect the build machine.
|
||||
UNSHARE := $(shell command -v unshare)
|
||||
ifneq (,$(UNSHARE))
|
||||
UNSHARE := $(UNSHARE) --uts
|
||||
endif
|
||||
|
||||
# sudo apt-get install qemu-user-static qemu-utils
|
||||
all: clean packer image
|
||||
|
||||
update_langs:
|
||||
@for lang in pwnagotchi/locale/*/; do\
|
||||
echo "updating language: $$lang ..."; \
|
||||
./scripts/language.sh update $$(basename $$lang); \
|
||||
done
|
||||
|
||||
compile_langs:
|
||||
@for lang in pwnagotchi/locale/*/; do\
|
||||
echo "compiling language: $$lang ..."; \
|
||||
./scripts/language.sh compile $$(basename $$lang); \
|
||||
done
|
||||
|
||||
packer:
|
||||
curl https://releases.hashicorp.com/packer/$(PACKER_VERSION)/packer_$(PACKER_VERSION)_linux_amd64.zip -o /tmp/packer.zip
|
||||
unzip /tmp/packer.zip -d /tmp
|
||||
sudo mv /tmp/packer /usr/bin/packer
|
||||
git clone https://github.com/solo-io/packer-builder-arm-image /tmp/packer-builder-arm-image
|
||||
cd /tmp/packer-builder-arm-image && go get -d ./... && go build
|
||||
|
||||
image: bullseye bookworm banagotchi
|
||||
|
||||
bullseye: clean packer
|
||||
export=LC_ALL=en_GB.utf-8
|
||||
cd builder && sudo /usr/bin/packer init data/32bit/pwnagotchi.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" data/32bit/pwnagotchi.json.pkr.hcl
|
||||
|
||||
bookworm: clean packer
|
||||
export=LC_ALL=en_GB.utf-8
|
||||
cd builder && sudo /usr/bin/packer init data/64bit/pwnagotchi.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" data/64bit/pwnagotchi.json.pkr.hcl
|
||||
|
||||
bananagotchi: clean packer
|
||||
export=LC_ALL=C.utf-8
|
||||
cd builder && sudo /usr/bin/packer init data/64bit/bananagotchi.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=bananagotchi" -var "pwn_version=$(PWN_VERSION)" data/64bit/bananagotchi.json.pkr.hcl
|
||||
|
||||
clean:
|
||||
- rm -rf /tmp/*
|
@ -11,7 +11,7 @@ Select ‘Credentials’ from the left menu, click ‘Create Credentials’, sel
|
||||
|
||||
Now, the product name and consent screen need to be set -> click ‘Configure consent screen’ and follow the instructions. Once finished:
|
||||
|
||||
Select ‘Application type’ to be Web application.
|
||||
Select ‘Application type’ to be Desktop application.
|
||||
|
||||
Enter an appropriate name.
|
||||
|
||||
@ -19,7 +19,7 @@ Input http://localhost/ for ‘Authorized redirect URIs’.
|
||||
|
||||
Select the correct oauth scope:
|
||||
|
||||
- drive.file
|
||||
- drive
|
||||
- drive.install
|
||||
|
||||
Click ‘Create’.
|
||||
|
32
README.md
32
README.md
@ -1,30 +1,8 @@
|
||||
# Pwnagotchi-Torch
|
||||
<a href="https://github.com/jayofelony/pwnagotchi-bookworm/releases/latest"><img alt="Release" src="https://img.shields.io/github/release/jayofelony/pwnagotchi-bookworm.svg"></a><br/>
|
||||
**This fork of [Pwnagotchi](https://www.pwnagotchi.ai) is only for 64-bit Raspberry Pi's. Such as the 02W, 3(b+) and 4(b) and the new Raspberry Pi 5!!.**
|
||||
|
||||
The RPi5 can only be used headless currently. (without display.)
|
||||
|
||||
|
||||
If you are using an older 32-bit version Raspberry Pi, ZeroWH, use this [fork](https://github.com/jayofelony/pwnagotchi-torch/releases/tag/v2.6.4) and make sure you download the `armhf` version.
|
||||
|
||||
---
|
||||
Download the latest image file [here](https://github.com/jayofelony/pwnagotchi-bookworm/releases/tag/v2.8.2), and let it auto-update from here on out.
|
||||
|
||||
**Use RPi imager to flash, please don't flash a new user as this will mess with logs created.**
|
||||
- Select `Use Custom Image`
|
||||
- Browse for the downloaded image file
|
||||
- Select No under `Use OS Customization`
|
||||
|
||||
SSH credentials are `pi/raspberry`.
|
||||
|
||||
# Donations:
|
||||
I would like to thank
|
||||
- [findingmoist](https://github.com/findingmoist)
|
||||
- [kr4k0n](https://github.com/kr4k0n)
|
||||
|
||||
for donating!
|
||||
|
||||
[Pwnagotchi-Torch](https://www.patreon.com/pwnagotchi_torch)
|
||||
# Pwnagotchi
|
||||
This is the main source for all forks:
|
||||
- [Bullseye](https://github.com/jayofelony/pwnagotchi-bullseye) RPiZeroW only (32bit)
|
||||
- [Bookworm](https://github.com/jayofelony/pwnagotchi-bookworm) RPiZero2W, RPi3, RPi4, RPi5 (64bit)
|
||||
- [Bananagotchi](https://github.com/jayofelony/bananagotchi) BPi M4 Zero
|
||||
|
||||
[GH Sponsor](https://github.com/sponsors/jayofelony)
|
||||
|
||||
|
229
bin/pwnagotchi
Executable file
229
bin/pwnagotchi
Executable file
@ -0,0 +1,229 @@
|
||||
#!/usr/bin/python3
|
||||
import logging
|
||||
import argparse
|
||||
import time
|
||||
import signal
|
||||
import sys
|
||||
import toml
|
||||
import requests
|
||||
import os
|
||||
|
||||
import pwnagotchi
|
||||
from pwnagotchi import utils
|
||||
from pwnagotchi.google import cmd as google_cmd
|
||||
from pwnagotchi.plugins import cmd as plugins_cmd
|
||||
from pwnagotchi import log
|
||||
from pwnagotchi import restart
|
||||
from pwnagotchi import fs
|
||||
from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple
|
||||
|
||||
|
||||
def do_clear(display):
|
||||
logging.info("clearing the display ...")
|
||||
display.clear()
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def do_manual_mode(agent):
|
||||
logging.info("entering manual mode ...")
|
||||
|
||||
agent.mode = 'manual'
|
||||
agent.last_session.parse(agent.view(), args.skip_session)
|
||||
if not args.skip_session:
|
||||
logging.info(
|
||||
"the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % (
|
||||
agent.last_session.duration_human,
|
||||
agent.last_session.epochs,
|
||||
agent.last_session.train_epochs,
|
||||
agent.last_session.avg_reward,
|
||||
agent.last_session.min_reward,
|
||||
agent.last_session.max_reward))
|
||||
|
||||
while True:
|
||||
display.on_manual_mode(agent.last_session)
|
||||
time.sleep(5)
|
||||
if grid.is_connected():
|
||||
plugins.on('internet_available', agent)
|
||||
|
||||
|
||||
def do_auto_mode(agent):
|
||||
logging.info("entering auto mode ...")
|
||||
|
||||
agent.mode = 'auto'
|
||||
agent.start()
|
||||
|
||||
while True:
|
||||
try:
|
||||
# recon on all channels
|
||||
agent.recon()
|
||||
# get nearby access points grouped by channel
|
||||
channels = agent.get_access_points_by_channel()
|
||||
# for each channel
|
||||
for ch, aps in channels:
|
||||
agent.set_channel(ch)
|
||||
|
||||
if not agent.is_stale() and agent.any_activity():
|
||||
logging.info("%d access points on channel %d" % (len(aps), ch))
|
||||
|
||||
# for each ap on this channel
|
||||
for ap in aps:
|
||||
# send an association frame in order to get for a PMKID
|
||||
agent.associate(ap)
|
||||
# deauth all client stations in order to get a full handshake
|
||||
for sta in ap['clients']:
|
||||
agent.deauth(ap, sta)
|
||||
time.sleep(1) # delay to not trigger nexmon firmware bugs
|
||||
|
||||
# An interesting effect of this:
|
||||
#
|
||||
# From Pwnagotchi's perspective, the more new access points
|
||||
# and / or client stations nearby, the longer one epoch of
|
||||
# its relative time will take ... basically, in Pwnagotchi's universe,
|
||||
# Wi-Fi electromagnetic fields affect time like gravitational fields
|
||||
# affect ours ... neat ^_^
|
||||
agent.next_epoch()
|
||||
|
||||
if grid.is_connected():
|
||||
plugins.on('internet_available', agent)
|
||||
|
||||
except Exception as e:
|
||||
if str(e).find("wifi.interface not set") > 0:
|
||||
logging.exception("main loop exception due to unavailable wifi device, likely programmatically disabled (%s)", e)
|
||||
logging.info("sleeping 60 seconds then advancing to next epoch to allow for cleanup code to trigger")
|
||||
time.sleep(60)
|
||||
agent.next_epoch()
|
||||
else:
|
||||
logging.exception("main loop exception (%s)", e)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
def add_parsers(parser):
|
||||
"""
|
||||
Adds the plugins and google subcommands
|
||||
"""
|
||||
subparsers = parser.add_subparsers()
|
||||
|
||||
# Add parsers from plugins_cmd
|
||||
plugins_cmd.add_parsers(subparsers)
|
||||
|
||||
# Add parsers from google_cmd
|
||||
google_cmd.add_parsers(subparsers)
|
||||
|
||||
parser = argparse.ArgumentParser(prog="pwnagotchi")
|
||||
# pwnagotchi --help
|
||||
parser.add_argument('-C', '--config', action='store', dest='config', default='/etc/pwnagotchi/default.toml',
|
||||
help='Main configuration file.')
|
||||
parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/etc/pwnagotchi/config.toml',
|
||||
help='If this file exists, configuration will be merged and this will override default values.')
|
||||
|
||||
parser.add_argument('--manual', dest="do_manual", action="store_true", default=False, help="Manual mode.")
|
||||
parser.add_argument('--skip-session', dest="skip_session", action="store_true", default=False,
|
||||
help="Skip last session parsing in manual mode.")
|
||||
|
||||
parser.add_argument('--clear', dest="do_clear", action="store_true", default=False,
|
||||
help="Clear the ePaper display and exit.")
|
||||
|
||||
parser.add_argument('--debug', dest="debug", action="store_true", default=False,
|
||||
help="Enable debug logs.")
|
||||
|
||||
parser.add_argument('--version', dest="version", action="store_true", default=False,
|
||||
help="Print the version.")
|
||||
|
||||
parser.add_argument('--print-config', dest="print_config", action="store_true", default=False,
|
||||
help="Print the configuration.")
|
||||
|
||||
# Jayofelony added these
|
||||
parser.add_argument('--check-update', dest="check_update", action="store_true", default=False,
|
||||
help="Check for updates on Pwnagotchi. And tells current version.")
|
||||
parser.add_argument('--donate', dest="donate", action="store_true", default=False,
|
||||
help="How to donate to this project.")
|
||||
|
||||
# pwnagotchi plugins --help
|
||||
add_parsers(parser)
|
||||
args = parser.parse_args()
|
||||
|
||||
if plugins_cmd.used_plugin_cmd(args):
|
||||
config = utils.load_config(args)
|
||||
log.setup_logging(args, config)
|
||||
rc = plugins_cmd.handle_cmd(args, config)
|
||||
sys.exit(rc)
|
||||
if google_cmd.used_google_cmd(args):
|
||||
config = utils.load_config(args)
|
||||
log.setup_logging(args, config)
|
||||
rc = google_cmd.handle_cmd(args)
|
||||
sys.exit(rc)
|
||||
|
||||
if args.version:
|
||||
print(pwnagotchi.__version__)
|
||||
sys.exit(0)
|
||||
|
||||
if args.donate:
|
||||
print("Donations can made @ \n "
|
||||
"https://www.patreon.com/pwnagotchi_torch \n "
|
||||
"https://github.com/sponsors/jayofelony \n\n"
|
||||
"But only if you really want to!")
|
||||
sys.exit(0)
|
||||
|
||||
if args.check_update:
|
||||
resp = requests.get("https://api.github.com/repos/jayofelony/pwnagotchi/releases/latest")
|
||||
latest = resp.json()
|
||||
latest_ver = latest['tag_name'].replace('v', '')
|
||||
|
||||
local = version_to_tuple(pwnagotchi.__version__)
|
||||
remote = version_to_tuple(latest_ver)
|
||||
if remote > local:
|
||||
user_input = input("There is a new version available! Update from v%s to v%s?\n[y(es)/n(o)]"
|
||||
% (pwnagotchi.__version__, latest_ver))
|
||||
# input validation
|
||||
if user_input.lower() in ('y', 'yes'):
|
||||
if os.path.exists('/root/.auto-update'):
|
||||
os.system("rm /root/.auto-update && systemctl restart pwnagotchi")
|
||||
else:
|
||||
logging.error("You should make sure auto-update is enabled!")
|
||||
print("Okay, give me a couple minutes. Just watch pwnlog while you wait.")
|
||||
elif user_input.lower() in ('n', 'no'): # using this elif for readability
|
||||
print("Okay, guess not!")
|
||||
else:
|
||||
print("You are currently on the latest release, v%s." % pwnagotchi.__version__)
|
||||
sys.exit(0)
|
||||
|
||||
config = utils.load_config(args)
|
||||
|
||||
if args.print_config:
|
||||
print(toml.dumps(config, encoder=DottedTomlEncoder()))
|
||||
sys.exit(0)
|
||||
|
||||
from pwnagotchi.identity import KeyPair
|
||||
from pwnagotchi.agent import Agent
|
||||
from pwnagotchi.ui import fonts
|
||||
from pwnagotchi.ui.display import Display
|
||||
from pwnagotchi import grid
|
||||
from pwnagotchi import plugins
|
||||
|
||||
pwnagotchi.config = config
|
||||
fs.setup_mounts(config)
|
||||
log.setup_logging(args, config)
|
||||
fonts.init(config)
|
||||
|
||||
pwnagotchi.set_name(config['main']['name'])
|
||||
|
||||
plugins.load(config)
|
||||
|
||||
display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()})
|
||||
|
||||
if args.do_clear:
|
||||
do_clear(display)
|
||||
sys.exit(0)
|
||||
|
||||
agent = Agent(view=display, config=config, keypair=KeyPair(view=display))
|
||||
|
||||
def usr1_handler(*unused):
|
||||
logging.info('Received USR1 singal. Restart process ...')
|
||||
restart("MANU" if args.do_manual else "AUTO")
|
||||
|
||||
signal.signal(signal.SIGUSR1, usr1_handler)
|
||||
|
||||
if args.do_manual:
|
||||
do_manual_mode(agent)
|
||||
else:
|
||||
do_auto_mode(agent)
|
@ -0,0 +1,36 @@
|
||||
_show_complete()
|
||||
{
|
||||
local cur opts node_names all_options opt_line
|
||||
all_options="
|
||||
pwnagotchi -h --help -C --config -U --user-config --manual --skip-session --clear --debug --version --print-config --check-update --donate {plugins,google}
|
||||
pwnagotchi plugins -h --help {list,install,enable,disable,uninstall,update,upgrade}
|
||||
pwnagotchi plugins list -i --installed -h --help
|
||||
pwnagotchi plugins install -h --help
|
||||
pwnagotchi plugins uninstall -h --help
|
||||
pwnagotchi plugins enable -h --help
|
||||
pwnagotchi plugins disable -h --help
|
||||
pwnagotchi plugins update -h --help
|
||||
pwnagotchi plugins upgrade -h --help
|
||||
pwnagotchi google -h --help {login,refresh}
|
||||
pwnagotchi google login -h --help
|
||||
pwnagotchi google refresh -h --help
|
||||
"
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
# shellcheck disable=SC2124
|
||||
cmd="${COMP_WORDS[@]:0:${#COMP_WORDS[@]}-1}"
|
||||
opt_line="$(grep -m1 "$cmd" <<<"$all_options")"
|
||||
if [[ ${cur} == -* ]] ; then
|
||||
opts="$(echo "$opt_line" | tr ' ' '\n' | awk '/^ *-/{gsub("[^a-zA-Z0-9-]","",$1);print $1}')"
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
opts="$(echo $opt_line | grep -Po '{\K[^}]+' | tr ',' '\n')"
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
}
|
||||
|
||||
complete -F _show_complete pwnagotchi
|
62
builder/data/32bit/etc/dhcpcd.conf
Normal file
62
builder/data/32bit/etc/dhcpcd.conf
Normal file
@ -0,0 +1,62 @@
|
||||
# A sample configuration for dhcpcd.
|
||||
# See dhcpcd.conf(5) for details.
|
||||
|
||||
# Allow users of this group to interact with dhcpcd via the control socket.
|
||||
#controlgroup wheel
|
||||
|
||||
# Inform the DHCP server of our hostname for DDNS.
|
||||
hostname
|
||||
|
||||
# Use the hardware address of the interface for the Client ID.
|
||||
clientid
|
||||
# or
|
||||
# Use the same DUID + IAID as set in DHCPv6 for DHCPv4 ClientID as per RFC4361.
|
||||
# Some non-RFC compliant DHCP servers do not reply with this set.
|
||||
# In this case, comment out duid and enable clientid above.
|
||||
#duid
|
||||
|
||||
# Persist interface configuration when dhcpcd exits.
|
||||
persistent
|
||||
|
||||
# Rapid commit support.
|
||||
# Safe to enable by default because it requires the equivalent option set
|
||||
# on the server to actually work.
|
||||
option rapid_commit
|
||||
|
||||
# A list of options to request from the DHCP server.
|
||||
option domain_name_servers, domain_name, domain_search, host_name
|
||||
option classless_static_routes
|
||||
# Respect the network MTU. This is applied to DHCP routes.
|
||||
option interface_mtu
|
||||
|
||||
# Most distributions have NTP support.
|
||||
#option ntp_servers
|
||||
|
||||
# A ServerID is required by RFC2131.
|
||||
require dhcp_server_identifier
|
||||
|
||||
# Generate SLAAC address using the Hardware Address of the interface
|
||||
#slaac hwaddr
|
||||
# OR generate Stable Private IPv6 Addresses based from the DUID
|
||||
slaac private
|
||||
|
||||
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
# !! DO NOT EDIT THESE LINES BELOW PLEASE !!
|
||||
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
# static IP configuration:
|
||||
denyinterfaces wlan0
|
||||
|
||||
interface eth0
|
||||
static domain_name_servers=8.8.8.8 1.1.1.1
|
||||
metric 201
|
||||
|
||||
interface usb0
|
||||
static ip_address=10.0.0.2/24
|
||||
static routers=10.0.0.1
|
||||
static domain_name_servers=10.0.0.1 8.8.8.8 1.1.1.1
|
||||
metric 202
|
||||
|
||||
interface bnep0
|
||||
static domain_name_servers=8.8.8.8 1.1.1.1
|
||||
metric 203
|
26
builder/data/32bit/etc/dphys-swapfile
Normal file
26
builder/data/32bit/etc/dphys-swapfile
Normal file
@ -0,0 +1,26 @@
|
||||
# /etc/dphys-swapfile - user settings for dphys-swapfile package
|
||||
# author Neil Franklin, last modification 2010.05.05
|
||||
# copyright ETH Zuerich Physics Departement
|
||||
# use under either modified/non-advertising BSD or GPL license
|
||||
|
||||
# this file is sourced with . so full normal sh syntax applies
|
||||
|
||||
# the default settings are added as commented out CONF_*=* lines
|
||||
|
||||
|
||||
# where we want the swapfile to be, this is the default
|
||||
#CONF_SWAPFILE=/var/swap
|
||||
|
||||
# set size to absolute value, leaving empty (default) then uses computed value
|
||||
# you most likely don't want this, unless you have an special disk situation
|
||||
CONF_SWAPSIZE=2048
|
||||
|
||||
# set size to computed value, this times RAM size, dynamically adapts,
|
||||
# guarantees that there is enough swap without wasting disk space on excess
|
||||
#CONF_SWAPFACTOR=2
|
||||
|
||||
# restrict size (computed and absolute!) to maximally this limit
|
||||
# can be set to empty for no limit, but beware of filled partitions!
|
||||
# this is/was a (outdated?) 32bit kernel limit (in MBytes), do not overrun it
|
||||
# but is also sensible on 64bit to prevent filling /var or even / partition
|
||||
#CONF_MAXSWAP=2048
|
13
builder/data/32bit/etc/systemd/system/bettercap.service
Normal file
13
builder/data/32bit/etc/systemd/system/bettercap.service
Normal file
@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=bettercap api.rest service.
|
||||
Documentation=https://bettercap.org
|
||||
Wants=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/bettercap-launcher
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
20
builder/data/32bit/etc/systemd/system/bluetooth.service
Normal file
20
builder/data/32bit/etc/systemd/system/bluetooth.service
Normal file
@ -0,0 +1,20 @@
|
||||
[Unit]
|
||||
Description=Bluetooth service
|
||||
Documentation=man:bluetoothd(8)
|
||||
ConditionPathIsDirectory=/sys/class/bluetooth
|
||||
|
||||
[Service]
|
||||
Type=dbus
|
||||
BusName=org.bluez
|
||||
ExecStart=/usr/libexec/bluetooth/bluetoothd --noplugin=sap,a2dp
|
||||
NotifyAccess=main
|
||||
#WatchdogSec=10
|
||||
#Restart=on-failure
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
||||
LimitNPROC=1
|
||||
ProtectHome=true
|
||||
ProtectSystem=full
|
||||
|
||||
[Install]
|
||||
WantedBy=bluetooth.target
|
||||
Alias=dbus-org.bluez.service
|
@ -0,0 +1,6 @@
|
||||
[Unit]
|
||||
After=hciuart.service bluetooth.service
|
||||
Before=
|
||||
|
||||
[Service]
|
||||
ExecStartPre=/bin/sleep 5
|
19
builder/data/32bit/etc/systemd/system/pwnagotchi.service
Normal file
19
builder/data/32bit/etc/systemd/system/pwnagotchi.service
Normal file
@ -0,0 +1,19 @@
|
||||
[Unit]
|
||||
Description=pwnagotchi Deep Reinforcement Learning instrumenting bettercap for WiFI pwning.
|
||||
Documentation=https://pwnagotchi.ai
|
||||
Wants=network.target
|
||||
After=pwngrid-peer.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=~
|
||||
ExecStart=/usr/bin/pwnagotchi-launcher
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
TasksMax=infinity
|
||||
LimitNPROC=infinity
|
||||
StandardOutput=null
|
||||
StandardError=null
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
16
builder/data/32bit/etc/systemd/system/pwngrid-peer.service
Normal file
16
builder/data/32bit/etc/systemd/system/pwngrid-peer.service
Normal file
@ -0,0 +1,16 @@
|
||||
[Unit]
|
||||
Description=pwngrid peer service.
|
||||
Documentation=https://pwnagotchi.ai
|
||||
Wants=network.target
|
||||
After=bettercap.service
|
||||
|
||||
[Service]
|
||||
Environment=LD_PRELOAD=/usr/local/lib/libpcap.so.1
|
||||
Environment=LD_LIBRARY_PATH=/usr/local/lib
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/pwngrid -keys /etc/pwnagotchi -peers /root/peers -address 127.0.0.1:8666 -client-token /root/.api-enrollment.json -wait -log /var/log/pwngrid-peer.log -iface wlan0mon
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
33
builder/data/32bit/etc/update-motd.d/01-motd
Executable file
33
builder/data/32bit/etc/update-motd.d/01-motd
Executable file
@ -0,0 +1,33 @@
|
||||
#!/bin/sh
|
||||
_hostname=$(hostname)
|
||||
_version=$(cut -d"'" -f2 < /usr/local/lib/python3.9/dist-packages/pwnagotchi/_version.py)
|
||||
echo
|
||||
echo "(◕‿‿◕) $_hostname"
|
||||
echo
|
||||
echo " Hi! I'm a pwnagotchi $_version, please take good care of me!"
|
||||
echo " Here are some basic things you need to know to raise me properly!"
|
||||
echo
|
||||
echo " If you want to change my configuration, use /etc/pwnagotchi/config.toml"
|
||||
echo " All plugin config files are located in /etc/pwnagotchi/conf.d/"
|
||||
echo " Read the readme if you want to use gdrivesync plugin!!"
|
||||
echo
|
||||
echo " All the configuration options can be found on /etc/pwnagotchi/default.toml,"
|
||||
echo " but don't change this file because I will recreate it every time I'm restarted!"
|
||||
echo
|
||||
echo " I use oPwnGrid as my main API, you can check stats at https://opwngrid.xyz"
|
||||
echo
|
||||
echo " I'm managed by systemd. Here are some basic commands."
|
||||
echo
|
||||
echo " If you want to know what I'm doing, you can check my logs with the command"
|
||||
echo " - pwnlog"
|
||||
echo " - sudo pwnagotchi --version, to check the current version"
|
||||
echo " - sudo pwnagotchi --donate, to see how you can donate to this project"
|
||||
echo " - sudo pwnagotchi --check-update, to see if there is a new version available"
|
||||
echo
|
||||
echo " If you want to know if I'm running, you can use"
|
||||
echo " sudo systemctl status pwnagotchi"
|
||||
echo
|
||||
echo " You can restart me using"
|
||||
echo " pwnkill"
|
||||
echo
|
||||
echo " You learn more about me at https://pwnagotchi.ai/"
|
40
builder/data/32bit/extras/nexmon.yml
Normal file
40
builder/data/32bit/extras/nexmon.yml
Normal file
@ -0,0 +1,40 @@
|
||||
# Install nexmon to fix wireless scanning (takes 2.5G of space)
|
||||
- name: clone nexmon repository
|
||||
git:
|
||||
repo: https://github.com/DrSchottky/nexmon.git
|
||||
dest: /usr/local/src/nexmon
|
||||
|
||||
- name: make firmware
|
||||
shell: "source ./setup_env.sh && make"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/nexmon/
|
||||
|
||||
- name: "make firmware patch ({{ item.name }})"
|
||||
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/{{ item.patch }}/nexmon/ && make"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/nexmon/
|
||||
environment:
|
||||
QEMU_UNAME: "{{ item.kernel }}"
|
||||
ARCHFLAGS: "{{ item.arch_flags }}"
|
||||
|
||||
- name: "install new firmware ({{ item.name }})"
|
||||
copy:
|
||||
src: "/usr/local/src/nexmon/patches/{{ item.patch }}/nexmon/{{ item.firmware }}"
|
||||
dest: "/usr/lib/firmware/brcm/{{ item.firmware }}"
|
||||
follow: true
|
||||
environment:
|
||||
QEMU_UNAME: "{{ item.kernel }}"
|
||||
ARCHFLAGS: "{{ item.arch_flags }}"
|
||||
|
||||
- name: backup original driver
|
||||
command: "mv /usr/lib/modules/{{ item.kernel }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz /usr/lib/modules/{{ item.kernel }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz.orig"
|
||||
|
||||
- name: copy modified driver
|
||||
copy:
|
||||
src: "/usr/local/src/nexmon/patches/driver/brcmfmac_6.1.y-nexmon/brcmfmac.ko"
|
||||
dest: "/usr/lib/modules/{{ item.kernel }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko"
|
||||
|
||||
- name : load brcmfmac drivers
|
||||
command: "/sbin/depmod -a {{ item.kernel }}"
|
94
builder/data/32bit/pwnagotchi.json.pkr.hcl
Normal file
94
builder/data/32bit/pwnagotchi.json.pkr.hcl
Normal file
@ -0,0 +1,94 @@
|
||||
packer {
|
||||
required_plugins {
|
||||
arm = {
|
||||
version = "1.0.0"
|
||||
source = "github.com/cdecoux/builder-arm"
|
||||
}
|
||||
ansible = {
|
||||
source = "github.com/hashicorp/ansible"
|
||||
version = "~> 1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "pwn_hostname" {
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "pwn_version" {
|
||||
type = string
|
||||
}
|
||||
|
||||
source "arm" "rpi32-pwnagotchi" {
|
||||
file_checksum_url = "https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2023-12-06/2023-12-05-raspios-bullseye-armhf-lite.img.xz.sha256"
|
||||
file_urls = ["https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2023-12-06/2023-12-05-raspios-bullseye-armhf-lite.img.xz"]
|
||||
file_checksum_type = "sha256"
|
||||
file_target_extension = "xz"
|
||||
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||
image_path = "../../pwnagotchi-rpi-bullseye-${var.pwn_version}-armhf.img"
|
||||
qemu_binary_source_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
|
||||
qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
|
||||
image_build_method = "resize"
|
||||
image_size = "9G"
|
||||
image_type = "dos"
|
||||
image_partitions {
|
||||
name = "boot"
|
||||
type = "c"
|
||||
start_sector = "8192"
|
||||
filesystem = "fat"
|
||||
size = "256M"
|
||||
mountpoint = "/boot"
|
||||
}
|
||||
image_partitions {
|
||||
name = "root"
|
||||
type = "83"
|
||||
start_sector = "532480"
|
||||
filesystem = "ext4"
|
||||
size = "0"
|
||||
mountpoint = "/"
|
||||
}
|
||||
}
|
||||
build {
|
||||
name = "Raspberry Pi 32 Pwnagotchi"
|
||||
sources = ["source.arm.rpi32-pwnagotchi"]
|
||||
provisioner "file" {
|
||||
destination = "/usr/bin/"
|
||||
sources = [
|
||||
"data/32bit/usr/bin/bettercap-launcher",
|
||||
"data/32bit/usr/bin/hdmioff",
|
||||
"data/32bit/usr/bin/hdmion",
|
||||
"data/32bit/usr/bin/monstart",
|
||||
"data/32bit/usr/bin/monstop",
|
||||
"data/32bit/usr/bin/pwnagotchi-launcher",
|
||||
"data/32bit/usr/bin/pwnlib",
|
||||
]
|
||||
}
|
||||
provisioner "shell" {
|
||||
inline = ["chmod +x /usr/bin/*"]
|
||||
}
|
||||
|
||||
provisioner "file" {
|
||||
destination = "/etc/systemd/system/"
|
||||
sources = [
|
||||
"data/32bit/etc/systemd/system/bettercap.service",
|
||||
"data/32bit/etc/systemd/system/pwnagotchi.service",
|
||||
"data/32bit/etc/systemd/system/pwngrid-peer.service",
|
||||
]
|
||||
}
|
||||
provisioner "file" {
|
||||
destination = "/etc/update-motd.d/01-motd"
|
||||
source = "data/32bit/etc/update-motd.d/01-motd"
|
||||
}
|
||||
provisioner "shell" {
|
||||
inline = ["chmod +x /etc/update-motd.d/*"]
|
||||
}
|
||||
provisioner "shell" {
|
||||
inline = ["apt-get -y --allow-releaseinfo-change update", "apt-get -y dist-upgrade", "apt-get install -y --no-install-recommends ansible"]
|
||||
}
|
||||
provisioner "ansible-local" {
|
||||
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
|
||||
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
|
||||
playbook_dir = "data/32bit/extras/"
|
||||
playbook_file = "data/32bit/pwnagotchi.yml"
|
||||
}
|
||||
}
|
606
builder/data/32bit/pwnagotchi.yml
Normal file
606
builder/data/32bit/pwnagotchi.yml
Normal file
@ -0,0 +1,606 @@
|
||||
---
|
||||
- hosts:
|
||||
- 127.0.0.1
|
||||
gather_facts: true
|
||||
become: true
|
||||
vars:
|
||||
boards:
|
||||
- {
|
||||
kernel: "6.1.21+",
|
||||
name: "PiZeroW",
|
||||
firmware: "brcmfmac43430-sdio.bin",
|
||||
patch: "bcm43430a1/7_45_41_46",
|
||||
cpu: arm1176,
|
||||
arch_flags: "-arch armv6l"
|
||||
}
|
||||
- {
|
||||
kernel: "6.1.21-v7+",
|
||||
name: "PiZero2W",
|
||||
firmware: "brcmfmac43436-sdio.bin",
|
||||
patch: "bcm43436b0/9_88_4_65",
|
||||
cpu: any, #cortex-a53
|
||||
arch_flags: "-arch armv7l"
|
||||
}
|
||||
- {
|
||||
kernel: "6.1.21-v7l+",
|
||||
name: "Pi4b_32",
|
||||
firmware: "brcmfmac43455-sdio.bin",
|
||||
patch: "bcm43455c0/7_45_206",
|
||||
cpu: any, #cortex-a72
|
||||
arch_flags: "-arch armv7l"
|
||||
}
|
||||
kernel:
|
||||
min: "6.1"
|
||||
full: "6.1.21+"
|
||||
full_2w: "6.1.21-v7+"
|
||||
full_4b: "6.1.21-v7l+"
|
||||
arch: "v6l"
|
||||
pwnagotchi:
|
||||
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
|
||||
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi-torch', true) }}"
|
||||
custom_plugin_dir: "/usr/local/share/pwnagotchi/custom-plugins"
|
||||
system:
|
||||
boot_options:
|
||||
- "#### pwnagotchi additions"
|
||||
- "# this pwnagotchi image is 32-bit only no v8+ headers to build nexmon for 64 bit"
|
||||
- "arm_64bit=0"
|
||||
- "# dwc2 for RNDIS. comment out, and remove dwc2 and g_ether from cmdline.txt for X306 usb battery hat"
|
||||
- "dtoverlay=dwc2"
|
||||
- "dtoverlay=spi1-3cs"
|
||||
- "dtparam=i2c1=on"
|
||||
- "dtparam=i2c_arm=on"
|
||||
- "dtparam=spi=on"
|
||||
- "gpu_mem=16"
|
||||
- "#### audio out on pins 18 and 19"
|
||||
- "#dtoverlay=audremap,pins_18_19"
|
||||
- "#### touchscreen on waveshare touch e-paper"
|
||||
- "#dtoverlay=goodix,interrupt=27,reset=22"
|
||||
- "#### for PWM backlighting on pimoroni displayhatmini"
|
||||
- "dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4"
|
||||
modules:
|
||||
- "i2c-dev"
|
||||
services:
|
||||
enable:
|
||||
- bettercap.service
|
||||
- bluetooth.service
|
||||
- dphys-swapfile.service
|
||||
- fstrim.timer
|
||||
- pwnagotchi.service
|
||||
- pwngrid-peer.service
|
||||
disable:
|
||||
- apt-daily-upgrade.service
|
||||
- apt-daily-upgrade.timer
|
||||
- apt-daily.service
|
||||
- apt-daily.timer
|
||||
- ifup@wlan0.service
|
||||
- triggerhappy.service
|
||||
- wpa_supplicant.service
|
||||
packages:
|
||||
caplets:
|
||||
source: "https://github.com/jayofelony/caplets.git"
|
||||
bettercap:
|
||||
source: "https://github.com/jayofelony/bettercap.git"
|
||||
url: "https://github.com/jayofelony/bettercap/releases/download/2.32.2/bettercap-2.32.2-armhf.zip"
|
||||
ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
|
||||
opwngrid:
|
||||
source: "https://github.com/jayofelony/pwngrid.git"
|
||||
url: "https://github.com/jayofelony/pwngrid/releases/download/v1.10.7/pwngrid-1.10.7-armhf.zip"
|
||||
torch:
|
||||
wheel: "torch-2.1.0a0+gitunknown-cp39-cp39-linux_armv6l.whl"
|
||||
url: "https://github.com/Sniffleupagus/Torch4Pizero/releases/download/v1.0.0/torch-2.1.0a0+gitunknown-cp39-cp39-linux_armv6l.whl"
|
||||
torchvision:
|
||||
wheel: "torchvision-0.16.0a0-cp39-cp39-linux_armv6l.whl"
|
||||
url: "https://github.com/Sniffleupagus/Torch4Pizero/releases/download/v1.0.0/torchvision-0.16.0a0-cp39-cp39-linux_armv6l.whl"
|
||||
apt:
|
||||
downgrade:
|
||||
- libpcap-dev_1.9.1-4_armhf.deb
|
||||
- libpcap0.8-dbg_1.9.1-4_armhf.deb
|
||||
- libpcap0.8-dev_1.9.1-4_armhf.deb
|
||||
- libpcap0.8_1.9.1-4_armhf.deb
|
||||
hold:
|
||||
- firmware-atheros
|
||||
- firmware-brcm80211
|
||||
- firmware-libertas
|
||||
- firmware-misc-nonfree
|
||||
- firmware-realtek
|
||||
- libpcap-dev
|
||||
- libpcap0.8
|
||||
- libpcap0.8-dev
|
||||
- libpcap0.8-dbg
|
||||
remove:
|
||||
- avahi-daemon
|
||||
- nfs-common
|
||||
- triggerhappy
|
||||
- wpasupplicant
|
||||
install:
|
||||
- autoconf
|
||||
- bc
|
||||
- bison
|
||||
- bluez
|
||||
- build-essential
|
||||
- curl
|
||||
- dkms
|
||||
- dphys-swapfile
|
||||
- espeak-ng
|
||||
- evtest
|
||||
- fbi
|
||||
- flex
|
||||
- fonts-dejavu
|
||||
- fonts-dejavu-core
|
||||
- fonts-dejavu-extra
|
||||
- fonts-freefont-ttf
|
||||
- g++
|
||||
- gawk
|
||||
- gcc-arm-none-eabi
|
||||
- git
|
||||
- libatlas-base-dev
|
||||
- libavcodec58
|
||||
- libavformat58
|
||||
- libblas-dev
|
||||
- libbluetooth-dev
|
||||
- libbz2-dev
|
||||
- libc-ares-dev
|
||||
- libc6-dev
|
||||
- libcpuinfo-dev
|
||||
- libdbus-1-dev
|
||||
- libdbus-glib-1-dev
|
||||
- libeigen3-dev
|
||||
- libelf-dev
|
||||
- libffi-dev
|
||||
- libfl-dev
|
||||
- libfuse-dev
|
||||
- libgdbm-dev
|
||||
- libgl1-mesa-glx
|
||||
- libgmp3-dev
|
||||
- libgstreamer1.0-0
|
||||
- libhdf5-dev
|
||||
- liblapack-dev
|
||||
- libncursesw5-dev
|
||||
- libnetfilter-queue-dev
|
||||
- libopenblas-dev
|
||||
- libopenjp2-7
|
||||
- libopenmpi-dev
|
||||
- libopenmpi3
|
||||
- libpcap-dev
|
||||
- libprotobuf-dev
|
||||
- libraspberrypi-bin
|
||||
- libraspberrypi-dev
|
||||
- libraspberrypi-doc
|
||||
- libraspberrypi0
|
||||
- libsleef-dev
|
||||
- libsqlite3-dev
|
||||
- libssl-dev
|
||||
- libswscale5
|
||||
- libtiff5
|
||||
- libtool
|
||||
- libts-bin
|
||||
- libusb-1.0-0-dev
|
||||
- lsof
|
||||
- make
|
||||
- python3-flask
|
||||
- python3-flask-cors
|
||||
- python3-flaskext.wtf
|
||||
- python3-pil
|
||||
- python3-pip
|
||||
- python3-protobuf
|
||||
- python3-smbus
|
||||
- qpdf
|
||||
- raspberrypi-kernel-headers
|
||||
- rsync
|
||||
- screen
|
||||
- tcpdump
|
||||
- texinfo
|
||||
- time
|
||||
- tk-dev
|
||||
- unzip
|
||||
- vim
|
||||
- wget
|
||||
- wl
|
||||
- xxd
|
||||
- zlib1g-dev
|
||||
|
||||
tasks:
|
||||
- name: Create pi user
|
||||
copy:
|
||||
dest: /boot/userconf
|
||||
content: |
|
||||
pi:$6$3jNr0GA9KIyt4hmM$efeVIopdMQ8DGgEPCWWlbx3mJJNAYci1lEXGdlky0xPyjqwKNbwTL5SrCcpb4144C4IvzWjn7Iv.QjqmU7iyT/
|
||||
|
||||
- name: change hostname
|
||||
lineinfile:
|
||||
dest: /etc/hostname
|
||||
regexp: '^raspberrypi'
|
||||
line: "{{pwnagotchi.hostname}}"
|
||||
state: present
|
||||
when: lookup('file', '/etc/hostname') == "raspberrypi"
|
||||
register: hostname
|
||||
|
||||
- name: add hostname to /etc/hosts
|
||||
lineinfile:
|
||||
dest: /etc/hosts
|
||||
regexp: '^127\.0\.1\.1[ \t]+raspberrypi'
|
||||
line: "127.0.1.1\t{{pwnagotchi.hostname}}"
|
||||
state: present
|
||||
when: hostname.changed
|
||||
|
||||
- name: Create custom plugin directory
|
||||
file:
|
||||
path: '{{ pwnagotchi.custom_plugin_dir }}'
|
||||
state: directory
|
||||
|
||||
- name: update apt package cache
|
||||
apt:
|
||||
update_cache: yes
|
||||
|
||||
- name: install packages
|
||||
apt:
|
||||
name: "{{ packages.apt.install }}"
|
||||
state: present
|
||||
|
||||
- name: update pip3, setuptools, wheel
|
||||
shell: "python3 -m pip install --upgrade pip setuptools wheel"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src
|
||||
|
||||
###########################################
|
||||
#
|
||||
# libpcap v1.9 - build from source
|
||||
#
|
||||
###########################################
|
||||
|
||||
# check for presence, then it can re-run in later parts if needed
|
||||
# use the "make" built in
|
||||
|
||||
# install libpcap before bettercap and pwngrid, so they use it
|
||||
- name: clone libpcap v1.9 from github
|
||||
git:
|
||||
repo: 'https://github.com/the-tcpdump-group/libpcap.git'
|
||||
dest: /usr/local/src/libpcap
|
||||
version: libpcap-1.9
|
||||
|
||||
- name: build and install libpcap into /usr/local/lib
|
||||
shell: "./configure && make && make install"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/libpcap
|
||||
|
||||
- name: remove libpcap build folder
|
||||
file:
|
||||
state: absent
|
||||
path: /usr/local/src/libpcap
|
||||
|
||||
- name: create symlink /usr/local/lib/libpcap.so.1.9.1
|
||||
file:
|
||||
src: /usr/local/lib/libpcap.so.1.9.1
|
||||
dest: /usr/local/lib/libpcap.so.0.8
|
||||
state: link
|
||||
|
||||
###############################################################
|
||||
# Install nexmon to fix wireless scanning (takes 2.5G of space)
|
||||
###############################################################
|
||||
|
||||
# Install nexmon for all boards
|
||||
- name: build and install nexmon as needed
|
||||
include_tasks: nexmon.yml
|
||||
loop: "{{ boards }}"
|
||||
|
||||
# some pizero2w have the pizeroW wifi chip
|
||||
# could this be a link instead of a copy? and force, only if not a link?
|
||||
- name: copy 43430-sdio as 43436s-sdio for the special 43430/1 /2
|
||||
copy:
|
||||
src: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin
|
||||
dest: /usr/lib/firmware/brcm/brcmfmac43436s-sdio.bin
|
||||
follow: true
|
||||
|
||||
# delete blob files that make nexmon sad
|
||||
- name: Delete the firmware blob files to avoid some nexmon crashing
|
||||
file:
|
||||
state: absent
|
||||
path: '{{ item }}'
|
||||
loop:
|
||||
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.clm_blob
|
||||
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,model-zero-w.clm_blob
|
||||
- /usr/lib/firmware/brcm/brcmfmac43430b0-sdio.raspberrypi,model-zero-2-w.clm_blob
|
||||
- /usr/lib/firmware/brcm/brcmfmac43436-sdio.raspberrypi,model-zero-2-w.clm_blob
|
||||
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,3-model-b.clm_blob
|
||||
|
||||
# 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: clone pwnagotchi repository
|
||||
git:
|
||||
repo: https://github.com/jayofelony/pwnagotchi.git
|
||||
dest: /usr/local/src/pwnagotchi
|
||||
register: pwnagotchigit
|
||||
|
||||
# is this even necessary? Can't we just link from /home/pi/pwnagotchi to /usr/local/{bin,lib,etc}
|
||||
# then just git update in the home dir and encourage hacking?
|
||||
# make owned by pi.pi, and custom plugins.
|
||||
- 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: download torch whl
|
||||
get_url:
|
||||
url: "{{ packages.torch.url }}"
|
||||
dest: /usr/local/src/
|
||||
|
||||
- name: download torchvision whl
|
||||
get_url:
|
||||
url: "{{ packages.torchvision.url }}"
|
||||
dest: /usr/local/src/
|
||||
|
||||
- name: install 32-bit pwnagotchi wheel and dependencies with 32-bit torch wheels
|
||||
pip:
|
||||
name:
|
||||
- "{{ lookup('fileglob', '/usr/local/src/pwnagotchi/dist/pwnagotchi*.whl') }}"
|
||||
- "{{ packages.torch.url }}"
|
||||
- "{{ packages.torchvision.url }}"
|
||||
extra_args: "--no-cache-dir"
|
||||
environment:
|
||||
QEMU_CPU: arm1176
|
||||
QEMU_UNAME: "{{ kernel.full }}"
|
||||
when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi_version)
|
||||
|
||||
- name: create /usr/local/share/pwnagotchi/ folder
|
||||
file:
|
||||
path: /usr/local/share/pwnagotchi/
|
||||
state: directory
|
||||
|
||||
- name: remove pwnagotchi folder
|
||||
file:
|
||||
state: absent
|
||||
path: /usr/local/src/pwnagotchi
|
||||
|
||||
- name: remove torch whl
|
||||
file:
|
||||
state: absent
|
||||
path: "{{ lookup('fileglob', '/usr/local/src/torch*.whl') }}"
|
||||
|
||||
##########################################
|
||||
#
|
||||
# pwngrid, bettercap
|
||||
#
|
||||
##########################################
|
||||
|
||||
- name: Install go-1.21
|
||||
unarchive:
|
||||
src: https://go.dev/dl/go1.21.6.linux-armv6l.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
|
||||
when: golang.changed
|
||||
|
||||
- name: download pwngrid
|
||||
unarchive:
|
||||
remote_src: yes
|
||||
src: "{{ packages.opwngrid.url }}"
|
||||
dest: /usr/local/bin/
|
||||
mode: 0755
|
||||
|
||||
- name: download and install bettercap
|
||||
unarchive:
|
||||
src: "{{ packages.bettercap.url }}"
|
||||
dest: /usr/local/bin
|
||||
remote_src: yes
|
||||
exclude:
|
||||
- README.md
|
||||
- LICENSE.md
|
||||
mode: 0755
|
||||
|
||||
- name: clone bettercap caplets
|
||||
git:
|
||||
repo: "{{ packages.caplets.source }}"
|
||||
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:
|
||||
src: "{{ packages.bettercap.ui }}"
|
||||
dest: /usr/local/share/bettercap/
|
||||
remote_src: yes
|
||||
mode: 0755
|
||||
|
||||
# to always have the bettercap webui available (because why not?)
|
||||
- name: copy pwnagotchi-manual over pwnagotchi-auto caplet
|
||||
ansible.builtin.copy:
|
||||
src: /usr/local/share/bettercap/caplets/pwnagotchi-manual.cap
|
||||
dest: /usr/local/share/bettercap/caplets/pwnagotchi-auto.cap
|
||||
force: true
|
||||
ignore_errors: true
|
||||
|
||||
- name: create /etc/pwnagotchi folder
|
||||
file:
|
||||
path: /etc/pwnagotchi
|
||||
state: directory
|
||||
|
||||
- name: create log folder
|
||||
file:
|
||||
path: /home/pi/logs
|
||||
state: directory
|
||||
|
||||
- name: check if user configuration exists
|
||||
stat:
|
||||
path: /etc/pwnagotchi/config.toml
|
||||
register: user_config
|
||||
|
||||
- name: create /etc/pwnagotchi/config.toml
|
||||
copy:
|
||||
dest: /etc/pwnagotchi/config.toml
|
||||
content: |
|
||||
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
|
||||
# Example:
|
||||
# ui.display.enabled = true
|
||||
# ui.display.type = "waveshare_2"
|
||||
when: not user_config.stat.exists
|
||||
|
||||
- name: Delete motd 10-uname
|
||||
file:
|
||||
state: absent
|
||||
path: /etc/update-motd.d/10-uname
|
||||
|
||||
- name: enable ssh on boot
|
||||
file:
|
||||
path: /boot/ssh
|
||||
state: touch
|
||||
|
||||
- name: adjust /boot/config.txt
|
||||
lineinfile:
|
||||
dest: /boot/config.txt
|
||||
insertafter: EOF
|
||||
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
|
||||
backup: no
|
||||
regexp: "root=PARTUUID=[a-zA-Z0-9\\-]+"
|
||||
replace: "root=/dev/mmcblk0p2"
|
||||
|
||||
- name: configure /boot/cmdline.txt
|
||||
lineinfile:
|
||||
path: /boot/cmdline.txt
|
||||
backrefs: True
|
||||
state: present
|
||||
backup: no
|
||||
regexp: '(.*)$'
|
||||
line: '\1 modules-load=dwc2,g_ether'
|
||||
|
||||
- name: Add pwnlog alias
|
||||
lineinfile:
|
||||
dest: /home/pi/.bashrc
|
||||
line: "\nalias pwnlog='tail -f -n300 /var/log/pwn*.log | sed --unbuffered \"s/,[[:digit:]]\\{3\\}\\]//g\" | cut -d \" \" -f 2-'"
|
||||
insertafter: EOF
|
||||
|
||||
- name: Add pwnver alias
|
||||
lineinfile:
|
||||
dest: /home/pi/.bashrc
|
||||
line: "\nalias pwnver='python3 -c \"import pwnagotchi as p; print(p.__version__)\"'"
|
||||
insertafter: EOF
|
||||
|
||||
- name: Add pwnkill alias to restart pwnagotchi with a signal
|
||||
lineinfile:
|
||||
dest: /home/pi/.bashrc
|
||||
line: "\nalias pwnkill='sudo killall -USR1 pwnagotchi'"
|
||||
insertafter: EOF
|
||||
|
||||
- name: add firmware packages to hold
|
||||
dpkg_selections:
|
||||
name: "{{ item }}"
|
||||
selection: hold
|
||||
with_items: "{{ packages.apt.hold }}"
|
||||
|
||||
- name: disable unnecessary services
|
||||
systemd:
|
||||
name: "{{ item }}"
|
||||
state: stopped
|
||||
enabled: no
|
||||
with_items: "{{ services.disable }}"
|
||||
|
||||
- name: enable services
|
||||
systemd:
|
||||
name: "{{ item }}"
|
||||
enabled: true
|
||||
state: stopped
|
||||
with_items: "{{ services.enable }}"
|
||||
|
||||
#- name: remove golang build libraries
|
||||
# file:
|
||||
# state: absent
|
||||
# path: /root/go
|
||||
|
||||
#- name: remove golang
|
||||
# file:
|
||||
# state: absent
|
||||
# path: /usr/local/go
|
||||
|
||||
- name: make /root readable, becauase that's where all the files are
|
||||
file:
|
||||
path: /root
|
||||
mode: '755'
|
||||
|
||||
- name: fix permissions on /home/pi
|
||||
file:
|
||||
path: /home/pi
|
||||
owner: pi
|
||||
group: pi
|
||||
recurse: true
|
||||
|
||||
- name: remove unnecessary apt packages
|
||||
apt:
|
||||
name: "{{ packages.apt.remove }}"
|
||||
state: absent
|
||||
purge: yes
|
||||
|
||||
- name: remove dependencies that are no longer required
|
||||
apt:
|
||||
autoremove: yes
|
||||
|
||||
- name: clean apt cache
|
||||
apt:
|
||||
autoclean: true
|
||||
|
||||
- name: remove golang build libraries
|
||||
file:
|
||||
state: absent
|
||||
path: /root/go
|
||||
|
||||
- name: remove pre-collected packages zip
|
||||
file:
|
||||
path: /root/go_pkgs.tgz
|
||||
state: absent
|
||||
|
||||
- name: remove golang
|
||||
file:
|
||||
state: absent
|
||||
path: /usr/local/go
|
||||
|
||||
- name: remove /root/.cache (pip cache)
|
||||
file:
|
||||
state: absent
|
||||
path: /root/.cache
|
||||
|
||||
- name: remove ssh keys
|
||||
file:
|
||||
state: absent
|
||||
path: "{{ item }}"
|
||||
with_fileglob:
|
||||
- "/etc/ssh/ssh_host*_key*"
|
||||
|
||||
- name: regenerate ssh keys
|
||||
shell: "dpkg-reconfigure openssh-server"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
|
||||
handlers:
|
||||
- name: reload systemd services
|
||||
systemd:
|
||||
daemon_reload: yes
|
0
builder/data/32bit/root/client_secrets.json
Normal file
0
builder/data/32bit/root/client_secrets.json
Normal file
15
builder/data/32bit/root/settings.yaml
Normal file
15
builder/data/32bit/root/settings.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
client_config_backend: file
|
||||
client_config_file: /root/client_secrets.json
|
||||
client_config:
|
||||
client_id: <YOUR CLIENT ID>
|
||||
client_secret: <YOUR CLIENT SECRET>
|
||||
|
||||
save_credentials: True
|
||||
save_credentials_backend: file
|
||||
save_credentials_file: /root/credentials.json
|
||||
|
||||
get_refresh_token: True
|
||||
|
||||
oauth_scope:
|
||||
- https://www.googleapis.com/auth/drive
|
||||
- https://www.googleapis.com/auth/drive.install
|
28
builder/data/32bit/usr/bin/bettercap-launcher
Executable file
28
builder/data/32bit/usr/bin/bettercap-launcher
Executable file
@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
source /usr/bin/pwnlib
|
||||
|
||||
# we need to decrypt something
|
||||
if is_crypted_mode; then
|
||||
while ! is_decrypted; do
|
||||
echo "Waiting for decryption..."
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
|
||||
# check if wifi driver is bugged
|
||||
if ! check_brcm; then
|
||||
if ! reload_brcm; then
|
||||
echo "Could not reload wifi driver. Reboot"
|
||||
reboot
|
||||
fi
|
||||
sleep 10
|
||||
fi
|
||||
|
||||
# start wlan0mon
|
||||
start_monitor_interface
|
||||
|
||||
if is_auto_mode_no_delete; then
|
||||
/usr/local/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface wlan0mon
|
||||
else
|
||||
/usr/local/bin/bettercap -no-colors -caplet pwnagotchi-manual -iface wlan0mon
|
||||
fi
|
149
builder/data/32bit/usr/bin/decryption-webserver
Executable file
149
builder/data/32bit/usr/bin/decryption-webserver
Executable file
@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from urllib.parse import parse_qsl
|
||||
|
||||
|
||||
_HTML_FORM_TEMPLATE = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Decryption</title>
|
||||
<style>
|
||||
body {{ text-align: center; padding: 150px; }}
|
||||
h1 {{ font-size: 50px; }}
|
||||
body {{ font: 20px Helvetica, sans-serif; color: #333; }}
|
||||
article {{ display: block; text-align: center; width: 650px; margin: 0 auto;}}
|
||||
input {{
|
||||
padding: 12px 20px;
|
||||
margin: 8px 0;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #ccc;
|
||||
}}
|
||||
input[type=password] {{
|
||||
width: 75%;
|
||||
font-size: 24px;
|
||||
}}
|
||||
input[type=submit] {{
|
||||
cursor: pointer;
|
||||
width: 75%;
|
||||
}}
|
||||
input[type=submit]:hover {{
|
||||
background-color: #d9d9d9;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<article>
|
||||
<h1>Decryption</h1>
|
||||
<p>Some of your files are encrypted.</p>
|
||||
<p>Please provide the decryption password.</p>
|
||||
<div>
|
||||
<form action="/set-password" method="POST">
|
||||
{password_fields}
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
POST_RESPONSE = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
/* Center the loader */
|
||||
#loader {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
z-index: 1;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
margin: -75px 0 0 -75px;
|
||||
border: 16px solid #f3f3f3;
|
||||
border-radius: 50%;
|
||||
border-top: 16px solid #3498db;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
-webkit-animation: spin 2s linear infinite;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
0% { -webkit-transform: rotate(0deg); }
|
||||
100% { -webkit-transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
#myDiv {
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
|
||||
function checkPwnagotchi() {
|
||||
var target = 'http://' + document.location.hostname + ':8080/';
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', target);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState == 4) {
|
||||
if (xhr.status == 200 || xhr.status == 401) {
|
||||
window.location.replace(target);
|
||||
}else{
|
||||
setTimeout(checkPwnagotchi, 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
setTimeout(checkPwnagotchi, 1000);
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body style="margin:0;">
|
||||
|
||||
<div id="loader"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
HTML_FORM = None
|
||||
|
||||
|
||||
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
|
||||
|
||||
def do_GET(self):
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(HTML_FORM.encode())
|
||||
|
||||
def do_POST(self):
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
body = self.rfile.read(content_length)
|
||||
for mapping, password in parse_qsl(body.decode('UTF-8')):
|
||||
with open('/tmp/.pwnagotchi-secret-{}'.format(mapping), 'wt') as pwfile:
|
||||
pwfile.write(password)
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(POST_RESPONSE.encode())
|
||||
|
||||
|
||||
with open('/root/.pwnagotchi-crypted') as crypted_file:
|
||||
mappings = [line.split()[0] for line in crypted_file.readlines()]
|
||||
fields = ''.join(['<label for="{m}">Passphrase for {m}:</label>\n<input type="password" id="{m}" name="{m}" value=""><br>'.format(m=m)
|
||||
for m in mappings])
|
||||
HTML_FORM = _HTML_FORM_TEMPLATE.format(password_fields=fields)
|
||||
|
||||
httpd = HTTPServer(('0.0.0.0', 80), SimpleHTTPRequestHandler)
|
||||
httpd.serve_forever()
|
2
builder/data/32bit/usr/bin/hdmioff
Executable file
2
builder/data/32bit/usr/bin/hdmioff
Executable file
@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
sudo /usr/bin/tvservice -o
|
2
builder/data/32bit/usr/bin/hdmion
Executable file
2
builder/data/32bit/usr/bin/hdmion
Executable file
@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
sudo /usr/bin/tvservice -p
|
3
builder/data/32bit/usr/bin/monstart
Executable file
3
builder/data/32bit/usr/bin/monstart
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
source /usr/bin/pwnlib
|
||||
start_monitor_interface
|
3
builder/data/32bit/usr/bin/monstop
Executable file
3
builder/data/32bit/usr/bin/monstop
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
source /usr/bin/pwnlib
|
||||
stop_monitor_interface
|
16
builder/data/32bit/usr/bin/pwnagotchi-launcher
Executable file
16
builder/data/32bit/usr/bin/pwnagotchi-launcher
Executable file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
source /usr/bin/pwnlib
|
||||
|
||||
# we need to decrypt something
|
||||
if is_crypted_mode; then
|
||||
while ! is_decrypted; do
|
||||
echo "Waiting for decryption..."
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
|
||||
if is_auto_mode; then
|
||||
/usr/local/bin/pwnagotchi
|
||||
else
|
||||
/usr/local/bin/pwnagotchi --manual
|
||||
fi
|
184
builder/data/32bit/usr/bin/pwnlib
Executable file
184
builder/data/32bit/usr/bin/pwnlib
Executable file
@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# check if brcm is stuck
|
||||
check_brcm() {
|
||||
if [[ "$(journalctl -n10 -k --since -5m | grep -c 'brcmf_cfg80211_nexmon_set_channel.*Set Channel failed')" -ge 5 ]]; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# reload mod
|
||||
reload_brcm() {
|
||||
if ! modprobe -r brcmfmac; then
|
||||
return 1
|
||||
fi
|
||||
sleep 1
|
||||
if ! modprobe brcmfmac; then
|
||||
return 1
|
||||
fi
|
||||
sleep 2
|
||||
iw dev wlan0 set power_save off
|
||||
return 0
|
||||
}
|
||||
|
||||
# starts mon0
|
||||
start_monitor_interface() {
|
||||
rfkill unblock all
|
||||
ifconfig wlan0 up
|
||||
iw dev wlan0 set power_save off
|
||||
iw phy "$(iw phy | head -1 | cut -d" " -f2)" interface add wlan0mon type monitor
|
||||
rfkill unblock all
|
||||
ifconfig wlan0 down
|
||||
ifconfig wlan0mon up
|
||||
iw dev wlan0mon set power_save off
|
||||
}
|
||||
|
||||
# stops mon0
|
||||
stop_monitor_interface() {
|
||||
ifconfig wlan0mon down && iw dev wlan0mon del
|
||||
reload_brcm
|
||||
ifconfig wlan0 up
|
||||
}
|
||||
|
||||
# returns 0 if the specificed network interface is up
|
||||
is_interface_up() {
|
||||
if grep -qi 'up' /sys/class/net/"$1"/operstate; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# returns 0 if conditions for AUTO mode are met
|
||||
is_auto_mode() {
|
||||
# check override file first
|
||||
if [ -f /root/.pwnagotchi-manual ]; then
|
||||
# remove the override file if found
|
||||
rm -rf /root/.pwnagotchi-manual
|
||||
return 1
|
||||
fi
|
||||
|
||||
# check override file first
|
||||
if [ -f /root/.pwnagotchi-auto ]; then
|
||||
# remove the override file if found
|
||||
rm -rf /root/.pwnagotchi-auto
|
||||
return 0
|
||||
fi
|
||||
|
||||
# if usb0 is up, we're in MANU
|
||||
if is_interface_up usb0; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# if eth0 is up (for other boards), we're in MANU
|
||||
if is_interface_up eth0; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# no override, but none of the interfaces is up -> AUTO
|
||||
return 0
|
||||
}
|
||||
|
||||
# returns 0 if conditions for AUTO mode are met
|
||||
is_auto_mode_no_delete() {
|
||||
# check override file first
|
||||
if [ -f /root/.pwnagotchi-manual ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# check override file first
|
||||
if [ -f /root/.pwnagotchi-auto ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# if usb0 is up, we're in MANU
|
||||
if is_interface_up usb0; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# if eth0 is up (for other boards), we're in MANU
|
||||
if is_interface_up eth0; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# no override, but none of the interfaces is up -> AUTO
|
||||
return 0
|
||||
}
|
||||
|
||||
# check if we need to decrypt something
|
||||
is_crypted_mode() {
|
||||
if [ -f /root/.pwnagotchi-crypted ]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# decryption loop
|
||||
is_decrypted() {
|
||||
while read -r mapping container mount; do
|
||||
# mapping = name the device or file will be mapped to
|
||||
# container = the luks encrypted device or file
|
||||
# mount = the mountpoint
|
||||
|
||||
# fail if not mounted
|
||||
if ! mountpoint -q "$mount" >/dev/null 2>&1; then
|
||||
if [ -f /tmp/.pwnagotchi-secret-"$mapping" ]; then
|
||||
</tmp/.pwnagotchi-secret-"$mapping" read -r SECRET
|
||||
if ! test -b /dev/disk/by-id/dm-uuid-*"$(cryptsetup luksUUID "$container" | tr -d -)"*; then
|
||||
if echo -n "$SECRET" | cryptsetup luksOpen -d- "$container" "$mapping" >/dev/null 2>&1; then
|
||||
echo "Container decrypted!"
|
||||
fi
|
||||
fi
|
||||
|
||||
if mount /dev/mapper/"$mapping" "$mount" >/dev/null 2>&1; then
|
||||
echo "Mounted /dev/mapper/$mapping to $mount"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! ip -4 addr show wlan0 | grep inet >/dev/null 2>&1; then
|
||||
>/dev/null 2>&1 ip addr add 192.168.0.10/24 dev wlan0
|
||||
fi
|
||||
|
||||
if ! pgrep -f decryption-webserver >/dev/null 2>&1; then
|
||||
>/dev/null 2>&1 decryption-webserver &
|
||||
fi
|
||||
|
||||
if ! pgrep wpa_supplicant >/dev/null 2>&1; then
|
||||
>/tmp/wpa_supplicant.conf cat <<EOF
|
||||
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
|
||||
update_config=1
|
||||
ap_scan=2
|
||||
|
||||
network={
|
||||
ssid="DECRYPT-ME"
|
||||
mode=2
|
||||
key_mgmt=WPA-PSK
|
||||
psk="pwnagotchi"
|
||||
frequency=2437
|
||||
}
|
||||
EOF
|
||||
>/dev/null 2>&1 wpa_supplicant -u -s -O -D nl80211 -i wlan0 -c /tmp/wpa_supplicant.conf &
|
||||
fi
|
||||
|
||||
if ! pgrep dnsmasq >/dev/null 2>&1; then
|
||||
>/dev/null 2>&1 dnsmasq -k -p 53 -h -O "6,192.168.0.10" -A "/#/192.168.0.10" -i wlan0 -K -F 192.168.0.50,192.168.0.60,255.255.255.0,24h &
|
||||
fi
|
||||
|
||||
return 1
|
||||
fi
|
||||
done </root/.pwnagotchi-crypted
|
||||
|
||||
# overwrite passwords
|
||||
python3 -c 'print("A"*4096)' | tee /tmp/.pwnagotchi-secret-* >/dev/null
|
||||
# delete
|
||||
rm /tmp/.pwnagotchi-secret-*
|
||||
sync # flush
|
||||
|
||||
pkill wpa_supplicant
|
||||
pkill dnsmasq
|
||||
pid="$(pgrep -f "decryption-webserver")"
|
||||
[[ -n "$pid" ]] && kill "$pid"
|
||||
|
||||
return 0
|
||||
}
|
80
builder/data/64bit/bananagotchi.json.pkr.hcl
Normal file
80
builder/data/64bit/bananagotchi.json.pkr.hcl
Normal file
@ -0,0 +1,80 @@
|
||||
packer {
|
||||
required_plugins {
|
||||
ansible = {
|
||||
source = "github.com/hashicorp/ansible"
|
||||
version = ">= 1.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "pwn_hostname" {
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "pwn_version" {
|
||||
type = string
|
||||
}
|
||||
|
||||
source "arm-image" "bananagotchi" {
|
||||
iso_checksum = "file:https://github.com/jayofelony/bananagotchi/releases/download/v1.0/bpim40.img.xz.sha256"
|
||||
iso_url = "https://github.com/jayofelony/bananagotchi/releases/download/v1.0/bpim40.img.xz"
|
||||
image_type = "armbian"
|
||||
image_arch = "arm64"
|
||||
qemu_args = ["-r", "6.1.31-sun50iw9"]
|
||||
target_image_size = 9368709120
|
||||
output_filename = "../../../bananagotchi-${var.pwn_version}.img"
|
||||
}
|
||||
|
||||
# a build block invokes sources and runs provisioning steps on them. The
|
||||
# documentation for build blocks can be found here:
|
||||
# https://www.packer.io/docs/from-1.5/blocks/build
|
||||
build {
|
||||
name = "bananagotchi"
|
||||
sources = ["source.arm-image.bananagotchi"]
|
||||
|
||||
|
||||
provisioner "file" {
|
||||
destination = "/usr/bin/"
|
||||
sources = [
|
||||
"data/64bit/usr/bin/bettercap-launcher",
|
||||
"data/64bit/usr/bin/hdmioff",
|
||||
"data/64bit/usr/bin/hdmion",
|
||||
"data/64bit/usr/bin/monstart",
|
||||
"data/64bit/usr/bin/monstop",
|
||||
"data/64bit/usr/bin/pwnagotchi-launcher",
|
||||
"data/64bit/usr/bin/pwnlib",
|
||||
]
|
||||
}
|
||||
provisioner "shell" {
|
||||
inline = ["chmod +x /usr/bin/*"]
|
||||
}
|
||||
|
||||
provisioner "file" {
|
||||
destination = "/etc/systemd/system/"
|
||||
sources = [
|
||||
"data/64bit/etc/systemd/system/bettercap.service",
|
||||
"data/64bit/etc/systemd/system/pwnagotchi.service",
|
||||
"data/64bit/etc/systemd/system/pwngrid-peer.service",
|
||||
]
|
||||
}
|
||||
|
||||
provisioner "file" {
|
||||
destination = "/etc/update-motd.d/01-motd"
|
||||
source = "data/64bit/etc/update-motd.d/01-motd"
|
||||
}
|
||||
provisioner "shell" {
|
||||
inline = ["chmod +x /etc/update-motd.d/*"]
|
||||
}
|
||||
provisioner "shell" {
|
||||
inline = [
|
||||
"apt-get -y --allow-releaseinfo-change update",
|
||||
"apt-get -y dist-upgrade",
|
||||
"apt-get install -y --no-install-recommends ansible"
|
||||
]
|
||||
}
|
||||
provisioner "ansible-local" {
|
||||
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
|
||||
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
|
||||
playbook_file = "data/64bit/bananagotchi.yml"
|
||||
}
|
||||
}
|
514
builder/data/64bit/bananagotchi.yml
Normal file
514
builder/data/64bit/bananagotchi.yml
Normal file
@ -0,0 +1,514 @@
|
||||
---
|
||||
- hosts:
|
||||
- 127.0.0.1
|
||||
gather_facts: true
|
||||
become: true
|
||||
vars:
|
||||
kernel:
|
||||
min: "6.1"
|
||||
full: "6.1.31-sun50iw9"
|
||||
pwnagotchi:
|
||||
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('bananagotchi', true) }}"
|
||||
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi-torch', true) }}"
|
||||
system:
|
||||
boot_options:
|
||||
- "dtoverlay=dwc2"
|
||||
- "dtoverlay=spi1-3cs"
|
||||
- "dtparam=i2c1=on"
|
||||
- "dtparam=i2c_arm=on"
|
||||
- "dtparam=spi=on"
|
||||
- "gpu_mem=16"
|
||||
modules:
|
||||
- "i2c-dev"
|
||||
services:
|
||||
enable:
|
||||
- bettercap.service
|
||||
- fstrim.timer
|
||||
- pwnagotchi.service
|
||||
- pwngrid-peer.service
|
||||
- zramswap.service
|
||||
disable:
|
||||
- apt-daily-upgrade.service
|
||||
- apt-daily-upgrade.timer
|
||||
- apt-daily.service
|
||||
- apt-daily.timer
|
||||
- bluetooth.service
|
||||
- ifup@wlan0.service
|
||||
packages:
|
||||
caplets:
|
||||
source: "https://github.com/jayofelony/caplets.git"
|
||||
bettercap:
|
||||
source: "https://github.com/jayofelony/bettercap.git"
|
||||
url: "https://github.com/jayofelony/bettercap/releases/download/2.32.2/bettercap-2.32.2.zip"
|
||||
ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
|
||||
pwngrid:
|
||||
source: "https://github.com/jayofelony/pwngrid.git"
|
||||
url: "https://github.com/jayofelony/pwngrid/releases/download/v1.10.5/pwngrid-1.10.5-aarch64.zip"
|
||||
apt:
|
||||
downgrade:
|
||||
- libpcap-dev_1.9.1-4_arm64.deb
|
||||
- libpcap0.8-dbg_1.9.1-4_arm64.deb
|
||||
- libpcap0.8-dev_1.9.1-4_arm64.deb
|
||||
- libpcap0.8_1.9.1-4_arm64.deb
|
||||
hold:
|
||||
- firmware-atheros
|
||||
- firmware-brcm80211
|
||||
- firmware-libertas
|
||||
- firmware-misc-nonfree
|
||||
- firmware-realtek
|
||||
- libpcap-dev
|
||||
- libpcap0.8
|
||||
- libpcap0.8-dbg
|
||||
- libpcap0.8-dev
|
||||
remove:
|
||||
- avahi-daemon
|
||||
- dhpys-swapfile
|
||||
- libcurl-ocaml-dev
|
||||
- libssl-ocaml-dev
|
||||
- nfs-common
|
||||
- triggerhappy
|
||||
- wpasupplicant
|
||||
install:
|
||||
- aircrack-ng
|
||||
- autoconf
|
||||
- bc
|
||||
- bison
|
||||
- bluez
|
||||
- build-essential
|
||||
- curl
|
||||
- dkms
|
||||
- fbi
|
||||
- flex
|
||||
- fonts-dejavu
|
||||
- fonts-dejavu-core
|
||||
- fonts-dejavu-extra
|
||||
- fonts-freefont-ttf
|
||||
- g++
|
||||
- gawk
|
||||
- gcc-arm-none-eabi
|
||||
- git
|
||||
- hcxtools
|
||||
- libatlas-base-dev
|
||||
- libavcodec59
|
||||
- libavformat59
|
||||
- libblas-dev
|
||||
- libbluetooth-dev
|
||||
- libbz2-dev
|
||||
- libc-ares-dev
|
||||
- libc6-dev
|
||||
- libcap-dev
|
||||
- libcurl-ocaml-dev
|
||||
- libdbus-1-dev
|
||||
- libdbus-glib-1-dev
|
||||
- libeigen3-dev
|
||||
- libelf-dev
|
||||
- libffi-dev
|
||||
- libfl-dev
|
||||
- libfuse-dev
|
||||
- libgdbm-dev
|
||||
- libgl1-mesa-glx
|
||||
- libgmp3-dev
|
||||
- libgstreamer1.0-0
|
||||
- libhdf5-dev
|
||||
- liblapack-dev
|
||||
- libncursesw5-dev
|
||||
- libnetfilter-queue-dev
|
||||
- libopenblas-dev
|
||||
- libopenjp2-7
|
||||
- libopenmpi-dev
|
||||
- libopenmpi3
|
||||
- libpcap-dev
|
||||
- libsqlite3-dev
|
||||
- libssl-dev
|
||||
- libssl-ocaml-dev
|
||||
- libtiff6
|
||||
- libtool
|
||||
- libusb-1.0-0-dev
|
||||
- lsof
|
||||
- make
|
||||
- python3-dbus
|
||||
- python3-flask
|
||||
- python3-flask-cors
|
||||
- python3-flaskext.wtf
|
||||
- python3-gast
|
||||
- python3-pil
|
||||
- python3-pip
|
||||
- python3-pycryptodome
|
||||
- python3-requests
|
||||
- python3-scapy
|
||||
- python3-setuptools
|
||||
- python3-smbus
|
||||
- python3-smbus2
|
||||
- python3-spidev
|
||||
- python3-tweepy
|
||||
- python3-werkzeug
|
||||
- python3-yaml
|
||||
- qpdf
|
||||
- rsync
|
||||
- screen
|
||||
- tcpdump
|
||||
- texinfo
|
||||
- time
|
||||
- tk-dev
|
||||
- unzip
|
||||
- vim
|
||||
- wget
|
||||
- wl
|
||||
- xxd
|
||||
- zlib1g-dev
|
||||
- zram-tools
|
||||
|
||||
tasks:
|
||||
# First we install packages
|
||||
- name: install packages
|
||||
apt:
|
||||
name: "{{ packages.apt.install }}"
|
||||
state: present
|
||||
update_cache: yes
|
||||
install_recommends: false
|
||||
|
||||
- name: set resolv.conf
|
||||
blockinfile:
|
||||
dest: /etc/resolv.conf
|
||||
state: present
|
||||
block: |
|
||||
nameserver 8.8.8.8
|
||||
nameserver 8.8.4.4
|
||||
insertafter: EOF
|
||||
|
||||
- name: set g_ether and i2c-dev
|
||||
blockinfile:
|
||||
dest: /etc/modules-load.d/modules.conf
|
||||
state: present
|
||||
insertafter: EOF
|
||||
block: |
|
||||
i2c-dev
|
||||
g_ether
|
||||
|
||||
- name: change hostname
|
||||
lineinfile:
|
||||
dest: /etc/hostname
|
||||
regexp: '^bananapim4zero'
|
||||
line: "{{pwnagotchi.hostname}}"
|
||||
state: present
|
||||
when: lookup('file', '/etc/hostname') == "bananapim4zero"
|
||||
register: hostname
|
||||
|
||||
- name: add hostname to /etc/hosts
|
||||
lineinfile:
|
||||
dest: /etc/hosts
|
||||
regexp: '^127\.0\.1\.1[ \t]+bananapim4zero'
|
||||
line: "127.0.1.1\t{{pwnagotchi.hostname}}"
|
||||
state: present
|
||||
when: hostname.changed
|
||||
|
||||
# Now we disable sap and a2dp, we don't use them on rpi
|
||||
- name: disable sap plugin for bluetooth.service
|
||||
lineinfile:
|
||||
dest: /lib/systemd/system/bluetooth.service
|
||||
regexp: '^ExecStart=/usr/libexec/bluetooth/bluetoothd$'
|
||||
line: 'ExecStart=/usr/libexec/bluetooth/bluetoothd --noplugin=sap,a2dp'
|
||||
state: present
|
||||
|
||||
###########################################
|
||||
#
|
||||
# libpcap v1.9 - build from source
|
||||
#
|
||||
###########################################
|
||||
|
||||
# check for presence, then it can re-run in later parts if needed
|
||||
# use the "make" built in
|
||||
|
||||
# install libpcap before bettercap and pwngrid, so they use it
|
||||
- name: clone libpcap v1.9 from github
|
||||
git:
|
||||
repo: 'https://github.com/the-tcpdump-group/libpcap.git'
|
||||
dest: /usr/local/src/libpcap
|
||||
version: libpcap-1.9
|
||||
|
||||
- name: build and install libpcap into /usr/local/lib
|
||||
shell: "./configure && make && make install"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/libpcap
|
||||
|
||||
- name: remove libpcap build folder
|
||||
file:
|
||||
state: absent
|
||||
path: /usr/local/src/libpcap
|
||||
|
||||
- name: create symlink /usr/local/lib/libpcap.so.1.9.1
|
||||
file:
|
||||
src: /usr/local/lib/libpcap.so.1.9.1
|
||||
dest: /usr/local/lib/libpcap.so.0.8
|
||||
state: link
|
||||
|
||||
# install latest hcxtools
|
||||
|
||||
- name: clone hcxtools
|
||||
git:
|
||||
repo: https://github.com/ZerBea/hcxtools.git
|
||||
dest: /usr/local/src/hcxtools
|
||||
|
||||
- name: install hcxtools
|
||||
shell: "make && make install"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/hcxtools
|
||||
|
||||
- name: remove hcxtools directory
|
||||
file:
|
||||
state: absent
|
||||
path: /usr/local/src/hcxtools
|
||||
|
||||
- name: Create custom plugin directory
|
||||
file:
|
||||
path: /usr/local/share/pwnagotchi/custom-plugins/
|
||||
state: directory
|
||||
|
||||
- name: Create custom config directory
|
||||
file:
|
||||
path: /etc/pwnagotchi/conf.d/
|
||||
state: directory
|
||||
|
||||
- name: clone pwnagotchi repository
|
||||
git:
|
||||
repo: https://github.com/jayofelony/pwnagotchi.git
|
||||
dest: /usr/local/src/pwnagotchi
|
||||
|
||||
- name: build pwnagotchi wheel
|
||||
command: "pip3 install . --no-cache-dir --break-system-packages"
|
||||
args:
|
||||
chdir: /usr/local/src/pwnagotchi
|
||||
|
||||
- name: remove pwnagotchi folder
|
||||
file:
|
||||
state: absent
|
||||
path: /usr/local/src/pwnagotchi
|
||||
|
||||
- name: create /usr/local/share/pwnagotchi/ folder
|
||||
file:
|
||||
path: /usr/local/share/pwnagotchi/
|
||||
state: directory
|
||||
|
||||
- name: Install go-1.21
|
||||
unarchive:
|
||||
src: https://go.dev/dl/go1.21.5.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
|
||||
when: golang.changed
|
||||
|
||||
- name: download pwngrid
|
||||
git:
|
||||
repo: "{{ packages.pwngrid.source }}"
|
||||
dest: /usr/local/src/pwngrid
|
||||
|
||||
- name: install pwngrid
|
||||
shell: "export GOPATH=$HOME/go && export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin && go mod tidy && make && make install"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/pwngrid
|
||||
|
||||
- name: remove pwngrid folder
|
||||
file:
|
||||
state: absent
|
||||
path: /usr/local/src/pwngrid
|
||||
|
||||
- name: download bettercap
|
||||
git:
|
||||
repo: "{{ packages.bettercap.source }}"
|
||||
dest: /usr/local/src/bettercap
|
||||
|
||||
- name: install bettercap 2.32.2
|
||||
shell: "export GOPATH=$HOME/go && export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin && go mod tidy && make && make install"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/bettercap
|
||||
|
||||
- name: remove bettercap folder
|
||||
file:
|
||||
state: absent
|
||||
path: /usr/local/src/bettercap
|
||||
|
||||
#- name: download and install bettercap
|
||||
# unarchive:
|
||||
# src: "{{ packages.bettercap.url }}"
|
||||
# dest: /usr/local/bin
|
||||
# remote_src: yes
|
||||
# exclude:
|
||||
# - README.md
|
||||
# - LICENSE.md
|
||||
# mode: 0755
|
||||
|
||||
- name: clone bettercap caplets
|
||||
git:
|
||||
repo: "{{ packages.caplets.source }}"
|
||||
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:
|
||||
src: "{{ packages.bettercap.ui }}"
|
||||
dest: /usr/local/share/bettercap/
|
||||
remote_src: yes
|
||||
mode: 0755
|
||||
|
||||
# to always have the bettercap webui available (because why not?)
|
||||
- name: copy pwnagotchi-manual over pwnagotchi-auto caplet
|
||||
ansible.builtin.copy:
|
||||
src: /usr/local/share/bettercap/caplets/pwnagotchi-manual.cap
|
||||
dest: /usr/local/share/bettercap/caplets/pwnagotchi-auto.cap
|
||||
force: true
|
||||
ignore_errors: true
|
||||
|
||||
- name: create /etc/pwnagotchi folder
|
||||
file:
|
||||
path: /etc/pwnagotchi
|
||||
state: directory
|
||||
|
||||
- name: check if user configuration exists
|
||||
stat:
|
||||
path: /etc/pwnagotchi/config.toml
|
||||
register: user_config
|
||||
|
||||
- name: create /etc/pwnagotchi/config.toml
|
||||
copy:
|
||||
dest: /etc/pwnagotchi/config.toml
|
||||
content: |
|
||||
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
|
||||
# Example:
|
||||
# ui.display.enabled = true
|
||||
# ui.display.type = "waveshare_2"
|
||||
when: not user_config.stat.exists
|
||||
|
||||
- name: Delete motd
|
||||
file:
|
||||
state: absent
|
||||
path: /etc/motd
|
||||
|
||||
- name: Delete motd 10-uname
|
||||
file:
|
||||
state: absent
|
||||
path: /etc/update-motd.d/10-uname
|
||||
|
||||
- name: Add pwnlog alias
|
||||
lineinfile:
|
||||
dest: /home/pi/.bashrc
|
||||
line: "\nalias pwnlog='tail -f -n300 /var/log/pwn*.log | sed --unbuffered \"s/,[[:digit:]]\\{3\\}\\]//g\" | cut -d \" \" -f 2-'"
|
||||
insertafter: EOF
|
||||
|
||||
- name: Add pwnver alias
|
||||
lineinfile:
|
||||
dest: /home/pi/.bashrc
|
||||
line: "\nalias pwnver='python3 -c \"import pwnagotchi as p; print(p.__version__)\"'"
|
||||
insertafter: EOF
|
||||
|
||||
- name: Add pwnkill alias to restart pwnagotchi with a signal
|
||||
lineinfile:
|
||||
dest: /home/pi/.bashrc
|
||||
line: "\nalias pwnkill='sudo killall -USR1 pwnagotchi'"
|
||||
insertafter: EOF
|
||||
|
||||
- name: add firmware packages to hold
|
||||
dpkg_selections:
|
||||
name: "{{ item }}"
|
||||
selection: hold
|
||||
with_items: "{{ packages.apt.hold }}"
|
||||
|
||||
- name: disable unnecessary services
|
||||
systemd:
|
||||
name: "{{ item }}"
|
||||
state: stopped
|
||||
enabled: no
|
||||
with_items: "{{ services.disable }}"
|
||||
|
||||
- name: enable services
|
||||
systemd:
|
||||
name: "{{ item }}"
|
||||
enabled: true
|
||||
state: stopped
|
||||
with_items: "{{ services.enable }}"
|
||||
register: enabled
|
||||
|
||||
- name: make /root readable, becauase that's where all the files are
|
||||
file:
|
||||
path: /root
|
||||
mode: '755'
|
||||
|
||||
- name: fix permissions on /home/pi
|
||||
file:
|
||||
path: /home/pi
|
||||
owner: pi
|
||||
group: pi
|
||||
recurse: true
|
||||
|
||||
- name: remove pre-collected packages zip
|
||||
file:
|
||||
path: /root/go_pkgs.tgz
|
||||
state: absent
|
||||
|
||||
- name: remove /root/go folder
|
||||
file:
|
||||
state: absent
|
||||
path: /root/go
|
||||
|
||||
- name: remove /usr/local/go folder
|
||||
file:
|
||||
state: absent
|
||||
path: /usr/local/go
|
||||
|
||||
- name: remove pip cache
|
||||
file:
|
||||
state: absent
|
||||
path: /root/.cache/pip
|
||||
|
||||
- name: remove ssh keys
|
||||
file:
|
||||
state: absent
|
||||
path: "{{ item }}"
|
||||
with_fileglob:
|
||||
- "/etc/ssh/ssh_host*_key*"
|
||||
|
||||
- name: regenerate ssh keys
|
||||
shell: "dpkg-reconfigure openssh-server"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
|
||||
# Now we remove packages
|
||||
- name: remove unnecessary apt packages
|
||||
apt:
|
||||
name: "{{ packages.apt.remove }}"
|
||||
state: absent
|
||||
purge: yes
|
||||
register: removed
|
||||
|
||||
- name: remove dependencies that are no longer required
|
||||
apt:
|
||||
autoremove: yes
|
||||
when: removed.changed
|
||||
|
||||
- name: clean apt cache
|
||||
apt:
|
||||
autoclean: true
|
||||
when: removed.changed
|
||||
|
||||
handlers:
|
||||
- name: reload systemd services
|
||||
systemd:
|
||||
daemon_reload: yes
|
||||
when: enabled.changed
|
@ -0,0 +1,5 @@
|
||||
[main]
|
||||
plugins=keyfile,ifupdown
|
||||
|
||||
[ifupdown]
|
||||
managed=true
|
@ -0,0 +1,36 @@
|
||||
_show_complete()
|
||||
{
|
||||
local cur opts node_names all_options opt_line
|
||||
all_options="
|
||||
pwnagotchi -h --help -C --config -U --user-config --manual --skip-session --clear --debug --version --print-config --check-update --donate {plugins,google}
|
||||
pwnagotchi plugins -h --help {list,install,enable,disable,uninstall,update,upgrade}
|
||||
pwnagotchi plugins list -i --installed -h --help
|
||||
pwnagotchi plugins install -h --help
|
||||
pwnagotchi plugins uninstall -h --help
|
||||
pwnagotchi plugins enable -h --help
|
||||
pwnagotchi plugins disable -h --help
|
||||
pwnagotchi plugins update -h --help
|
||||
pwnagotchi plugins upgrade -h --help
|
||||
pwnagotchi google -h --help {login,refresh}
|
||||
pwnagotchi google login -h --help
|
||||
pwnagotchi google refresh -h --help
|
||||
"
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
# shellcheck disable=SC2124
|
||||
cmd="${COMP_WORDS[@]:0:${#COMP_WORDS[@]}-1}"
|
||||
opt_line="$(grep -m1 "$cmd" <<<"$all_options")"
|
||||
if [[ ${cur} == -* ]] ; then
|
||||
opts="$(echo "$opt_line" | tr ' ' '\n' | awk '/^ *-/{gsub("[^a-zA-Z0-9-]","",$1);print $1}')"
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
opts="$(echo $opt_line | grep -Po '{\K[^}]+' | tr ',' '\n')"
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
}
|
||||
|
||||
complete -F _show_complete pwnagotchi
|
26
builder/data/64bit/etc/dphys-swapfile
Normal file
26
builder/data/64bit/etc/dphys-swapfile
Normal file
@ -0,0 +1,26 @@
|
||||
# /etc/dphys-swapfile - user settings for dphys-swapfile package
|
||||
# author Neil Franklin, last modification 2010.05.05
|
||||
# copyright ETH Zuerich Physics Departement
|
||||
# use under either modified/non-advertising BSD or GPL license
|
||||
|
||||
# this file is sourced with . so full normal sh syntax applies
|
||||
|
||||
# the default settings are added as commented out CONF_*=* lines
|
||||
|
||||
|
||||
# where we want the swapfile to be, this is the default
|
||||
#CONF_SWAPFILE=/var/swap
|
||||
|
||||
# set size to absolute value, leaving empty (default) then uses computed value
|
||||
# you most likely don't want this, unless you have an special disk situation
|
||||
CONF_SWAPSIZE=2048
|
||||
|
||||
# set size to computed value, this times RAM size, dynamically adapts,
|
||||
# guarantees that there is enough swap without wasting disk space on excess
|
||||
#CONF_SWAPFACTOR=2
|
||||
|
||||
# restrict size (computed and absolute!) to maximally this limit
|
||||
# can be set to empty for no limit, but beware of filled partitions!
|
||||
# this is/was a (outdated?) 32bit kernel limit (in MBytes), do not overrun it
|
||||
# but is also sensible on 64bit to prevent filling /var or even / partition
|
||||
#CONF_MAXSWAP=2048
|
2
builder/data/64bit/etc/network/interfaces.d/eth0-cfg
Normal file
2
builder/data/64bit/etc/network/interfaces.d/eth0-cfg
Normal file
@ -0,0 +1,2 @@
|
||||
allow-hotplug eth0
|
||||
iface eth0 inet dhcp
|
2
builder/data/64bit/etc/network/interfaces.d/lo-cfg
Normal file
2
builder/data/64bit/etc/network/interfaces.d/lo-cfg
Normal file
@ -0,0 +1,2 @@
|
||||
auto lo
|
||||
iface lo inet loopback
|
8
builder/data/64bit/etc/network/interfaces.d/usb0-cfg
Normal file
8
builder/data/64bit/etc/network/interfaces.d/usb0-cfg
Normal file
@ -0,0 +1,8 @@
|
||||
allow-hotplug usb0
|
||||
iface usb0 inet static
|
||||
address 10.0.0.2
|
||||
netmask 255.255.255.0
|
||||
network 10.0.0.0
|
||||
broadcast 10.0.0.255
|
||||
gateway 10.0.0.1
|
||||
metric 101
|
2
builder/data/64bit/etc/network/interfaces.d/wlan0-cfg
Normal file
2
builder/data/64bit/etc/network/interfaces.d/wlan0-cfg
Normal file
@ -0,0 +1,2 @@
|
||||
allow-hotplug wlan0
|
||||
iface wlan0 inet static
|
13
builder/data/64bit/etc/systemd/system/bettercap.service
Normal file
13
builder/data/64bit/etc/systemd/system/bettercap.service
Normal file
@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=bettercap api.rest service.
|
||||
Documentation=https://bettercap.org
|
||||
Wants=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/bettercap-launcher
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
20
builder/data/64bit/etc/systemd/system/pwnagotchi.service
Normal file
20
builder/data/64bit/etc/systemd/system/pwnagotchi.service
Normal file
@ -0,0 +1,20 @@
|
||||
[Unit]
|
||||
Description=pwnagotchi Deep Reinforcement Learning instrumenting bettercap for WiFI pwning.
|
||||
Documentation=https://pwnagotchi.org
|
||||
Wants=network.target
|
||||
After=pwngrid-peer.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=~
|
||||
ExecStart=/usr/bin/pwnagotchi-launcher
|
||||
ExecStopPost=/usr/bin/bash -c "if egrep -qi 'personality.clear_on_exit[ =]*true' /etc/pwnagotchi/config.toml ; then /usr/local/bin/pwnagotchi --clear; fi"
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
TasksMax=infinity
|
||||
LimitNPROC=infinity
|
||||
StandardOutput=null
|
||||
StandardError=null
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
16
builder/data/64bit/etc/systemd/system/pwngrid-peer.service
Normal file
16
builder/data/64bit/etc/systemd/system/pwngrid-peer.service
Normal file
@ -0,0 +1,16 @@
|
||||
[Unit]
|
||||
Description=pwngrid peer service.
|
||||
Documentation=https://pwnagotchi.ai
|
||||
Wants=network.target
|
||||
After=bettercap.service
|
||||
|
||||
[Service]
|
||||
Environment=LD_PRELOAD=/usr/local/lib/libpcap.so.1
|
||||
Environment=LD_LIBRARY_PATH=/usr/local/lib
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/pwngrid -keys /etc/pwnagotchi -peers /root/peers -address 127.0.0.1:8666 -client-token /root/.api-enrollment.json -wait -log /home/pi/logs/pwngrid-peer.log -iface wlan0mon
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
33
builder/data/64bit/etc/update-motd.d/01-motd
Executable file
33
builder/data/64bit/etc/update-motd.d/01-motd
Executable file
@ -0,0 +1,33 @@
|
||||
#!/bin/sh
|
||||
_hostname=$(hostname)
|
||||
_version=$(cut -d"'" -f2 < /usr/local/lib/python3.11/dist-packages/pwnagotchi/_version.py)
|
||||
echo
|
||||
echo "(☉_☉ ) $_hostname"
|
||||
echo
|
||||
echo " Hi! I'm a pwnagotchi $_version, please take good care of me!"
|
||||
echo " Here are some basic things you need to know to raise me properly!"
|
||||
echo
|
||||
echo " If you want to change my configuration, use /etc/pwnagotchi/config.toml"
|
||||
echo " All plugin config files are located in /etc/pwnagotchi/conf.d/"
|
||||
echo " Read the readme if you want to use gdrivesync plugin!!"
|
||||
echo
|
||||
echo " All the configuration options can be found on /etc/pwnagotchi/default.toml,"
|
||||
echo " but don't change this file because I will recreate it every time I'm restarted!"
|
||||
echo
|
||||
echo " I use oPwnGrid as my main API, you can check stats at https://opwngrid.xyz"
|
||||
echo
|
||||
echo " I'm managed by systemd. Here are some basic commands."
|
||||
echo
|
||||
echo " If you want to know what I'm doing, you can check my logs with the command"
|
||||
echo " - pwnlog"
|
||||
echo " - sudo pwnagotchi --version, to check the current version"
|
||||
echo " - sudo pwnagotchi --donate, to see how you can donate to this project"
|
||||
echo " - sudo pwnagotchi --check-update, to see if there is a new version available"
|
||||
echo
|
||||
echo " If you want to know if I'm running, you can use"
|
||||
echo " sudo systemctl status pwnagotchi"
|
||||
echo
|
||||
echo " You can restart me using"
|
||||
echo " pwnkill"
|
||||
echo
|
||||
echo " You can learn more about me at https://pwnagotchi.org/"
|
100
builder/data/64bit/pwnagotchi.json.pkr.hcl
Normal file
100
builder/data/64bit/pwnagotchi.json.pkr.hcl
Normal file
@ -0,0 +1,100 @@
|
||||
packer {
|
||||
required_plugins {
|
||||
arm = {
|
||||
version = "1.0.0"
|
||||
source = "github.com/cdecoux/builder-arm"
|
||||
}
|
||||
ansible = {
|
||||
source = "github.com/hashicorp/ansible"
|
||||
version = "~> 1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "pwn_hostname" {
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "pwn_version" {
|
||||
type = string
|
||||
}
|
||||
|
||||
source "arm" "rpi64-pwnagotchi" {
|
||||
file_checksum_url = "https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-12-11/2023-12-11-raspios-bookworm-arm64-lite.img.xz.sha256"
|
||||
file_urls = ["https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-12-11/2023-12-11-raspios-bookworm-arm64-lite.img.xz"]
|
||||
file_checksum_type = "sha256"
|
||||
file_target_extension = "xz"
|
||||
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||
image_path = "../../../pwnagotchi-rpi-bookworm-${var.pwn_version}-arm64.img"
|
||||
qemu_binary_source_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
|
||||
qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
|
||||
image_build_method = "resize"
|
||||
image_size = "9G"
|
||||
image_type = "dos"
|
||||
image_partitions {
|
||||
name = "boot"
|
||||
type = "c"
|
||||
start_sector = "8192"
|
||||
filesystem = "fat"
|
||||
size = "256M"
|
||||
mountpoint = "/boot/firmware"
|
||||
}
|
||||
image_partitions {
|
||||
name = "root"
|
||||
type = "83"
|
||||
start_sector = "532480"
|
||||
filesystem = "ext4"
|
||||
size = "0"
|
||||
mountpoint = "/"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
# a build block invokes sources and runs provisioning steps on them. The
|
||||
# documentation for build blocks can be found here:
|
||||
# https://www.packer.io/docs/from-1.5/blocks/build
|
||||
build {
|
||||
name = "Raspberry Pi 64 Pwnagotchi"
|
||||
sources = ["source.arm.rpi64-pwnagotchi"]
|
||||
|
||||
provisioner "file" {
|
||||
destination = "/usr/bin/"
|
||||
sources = [
|
||||
"data/64bit/usr/bin/bettercap-launcher",
|
||||
"data/64bit/usr/bin/hdmioff",
|
||||
"data/64bit/usr/bin/hdmion",
|
||||
"data/64bit/usr/bin/monstart",
|
||||
"data/64bit/usr/bin/monstop",
|
||||
"data/64bit/usr/bin/pwnagotchi-launcher",
|
||||
"data/64bit/usr/bin/pwnlib",
|
||||
]
|
||||
}
|
||||
provisioner "shell" {
|
||||
inline = ["chmod +x /usr/bin/*"]
|
||||
}
|
||||
|
||||
provisioner "file" {
|
||||
destination = "/etc/systemd/system/"
|
||||
sources = [
|
||||
"data/64bit/etc/systemd/system/bettercap.service",
|
||||
"data/64bit/etc/systemd/system/pwnagotchi.service",
|
||||
"data/64bit/etc/systemd/system/pwngrid-peer.service",
|
||||
]
|
||||
}
|
||||
provisioner "file" {
|
||||
destination = "/etc/update-motd.d/01-motd"
|
||||
source = "data/64bit/etc/update-motd.d/01-motd"
|
||||
}
|
||||
provisioner "shell" {
|
||||
inline = ["chmod +x /etc/update-motd.d/*"]
|
||||
}
|
||||
provisioner "shell" {
|
||||
inline = ["apt-get -y --allow-releaseinfo-change update", "apt-get -y dist-upgrade", "apt-get install -y --no-install-recommends ansible"]
|
||||
}
|
||||
provisioner "ansible-local" {
|
||||
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
|
||||
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
|
||||
playbook_file = "data/64bit/raspberrypi64.yml"
|
||||
}
|
||||
}
|
700
builder/data/64bit/raspberrypi64.yml
Normal file
700
builder/data/64bit/raspberrypi64.yml
Normal file
@ -0,0 +1,700 @@
|
||||
---
|
||||
- hosts:
|
||||
- 127.0.0.1
|
||||
gather_facts: true
|
||||
become: true
|
||||
vars:
|
||||
kernel:
|
||||
min: "6.1"
|
||||
full: "6.1.0-rpi8-rpi-v8"
|
||||
full_pi5: "6.1.0-rpi8-rpi-2712"
|
||||
pwnagotchi:
|
||||
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
|
||||
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi-torch', true) }}"
|
||||
system:
|
||||
boot_options:
|
||||
- "dtoverlay=dwc2"
|
||||
- "dtoverlay=spi1-3cs"
|
||||
- "dtparam=i2c1=on"
|
||||
- "dtparam=i2c_arm=on"
|
||||
- "dtparam=spi=on"
|
||||
- "gpu_mem=16"
|
||||
modules:
|
||||
- "i2c-dev"
|
||||
services:
|
||||
enable:
|
||||
- bettercap.service
|
||||
- fstrim.timer
|
||||
- pwnagotchi.service
|
||||
- pwngrid-peer.service
|
||||
disable:
|
||||
- apt-daily-upgrade.service
|
||||
- apt-daily-upgrade.timer
|
||||
- apt-daily.service
|
||||
- apt-daily.timer
|
||||
- bluetooth.service
|
||||
- ifup@wlan0.service
|
||||
packages:
|
||||
caplets:
|
||||
source: "https://github.com/jayofelony/caplets.git"
|
||||
bettercap:
|
||||
source: "https://github.com/jayofelony/bettercap.git"
|
||||
url: "https://github.com/jayofelony/bettercap/releases/download/2.32.4/bettercap-2.32.4.zip"
|
||||
ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
|
||||
pwngrid:
|
||||
source: "https://github.com/jayofelony/pwngrid.git"
|
||||
url: "https://github.com/jayofelony/pwngrid/releases/download/v1.10.5/pwngrid-1.10.5-aarch64.zip"
|
||||
apt:
|
||||
downgrade:
|
||||
- libpcap-dev_1.9.1-4_arm64.deb
|
||||
- libpcap0.8-dbg_1.9.1-4_arm64.deb
|
||||
- libpcap0.8-dev_1.9.1-4_arm64.deb
|
||||
- libpcap0.8_1.9.1-4_arm64.deb
|
||||
hold:
|
||||
- firmware-atheros
|
||||
- firmware-brcm80211
|
||||
- firmware-libertas
|
||||
- firmware-misc-nonfree
|
||||
- firmware-realtek
|
||||
- libpcap-dev
|
||||
- libpcap0.8
|
||||
- libpcap0.8-dbg
|
||||
- libpcap0.8-dev
|
||||
remove:
|
||||
- avahi-daemon
|
||||
- dhpys-swapfile
|
||||
- libcurl-ocaml-dev
|
||||
- libssl-ocaml-dev
|
||||
- nfs-common
|
||||
- triggerhappy
|
||||
- wpasupplicant
|
||||
install:
|
||||
- aircrack-ng
|
||||
- autoconf
|
||||
- bc
|
||||
- bison
|
||||
- bluez
|
||||
- build-essential
|
||||
- curl
|
||||
- dkms
|
||||
- dphys-swapfile
|
||||
- fbi
|
||||
- firmware-atheros
|
||||
- firmware-brcm80211
|
||||
- firmware-libertas
|
||||
- firmware-misc-nonfree
|
||||
- firmware-realtek
|
||||
- flex
|
||||
- fonts-dejavu
|
||||
- fonts-dejavu-core
|
||||
- fonts-dejavu-extra
|
||||
- fonts-freefont-ttf
|
||||
- g++
|
||||
- gawk
|
||||
- gcc-arm-none-eabi
|
||||
- git
|
||||
- hcxtools
|
||||
- libatlas-base-dev
|
||||
- libavcodec59
|
||||
- libavformat59
|
||||
- libblas-dev
|
||||
- libbluetooth-dev
|
||||
- libbz2-dev
|
||||
- libc-ares-dev
|
||||
- libc6-dev
|
||||
- libcap-dev
|
||||
- libcurl-ocaml-dev
|
||||
- libdbus-1-dev
|
||||
- libdbus-glib-1-dev
|
||||
- libeigen3-dev
|
||||
- libelf-dev
|
||||
- libffi-dev
|
||||
- libfl-dev
|
||||
- libfuse-dev
|
||||
- libgdbm-dev
|
||||
- libgl1-mesa-glx
|
||||
- libgmp3-dev
|
||||
- libgstreamer1.0-0
|
||||
- libhdf5-dev
|
||||
- liblapack-dev
|
||||
- libncursesw5-dev
|
||||
- libnetfilter-queue-dev
|
||||
- libopenblas-dev
|
||||
- libopenjp2-7
|
||||
- libopenmpi-dev
|
||||
- libopenmpi3
|
||||
- libpcap-dev
|
||||
- libraspberrypi-bin
|
||||
- libraspberrypi-dev
|
||||
- libraspberrypi-doc
|
||||
- libraspberrypi0
|
||||
- libsqlite3-dev
|
||||
- libssl-dev
|
||||
- libssl-ocaml-dev
|
||||
- libswscale5
|
||||
- libtiff6
|
||||
- libtool
|
||||
- libusb-1.0-0-dev
|
||||
- lsof
|
||||
- make
|
||||
- python3-dbus
|
||||
- python3-flask
|
||||
- python3-flask-cors
|
||||
- python3-flaskext.wtf
|
||||
- python3-gast
|
||||
- python3-pil
|
||||
- python3-pip
|
||||
- python3-pycryptodome
|
||||
- python3-requests
|
||||
- python3-scapy
|
||||
- python3-setuptools
|
||||
- python3-smbus
|
||||
- python3-smbus2
|
||||
- python3-spidev
|
||||
- python3-tweepy
|
||||
- python3-werkzeug
|
||||
- python3-yaml
|
||||
- qpdf
|
||||
- raspberrypi-kernel-headers
|
||||
- rsync
|
||||
- screen
|
||||
- tcpdump
|
||||
- texinfo
|
||||
- time
|
||||
- tk-dev
|
||||
- unzip
|
||||
- vim
|
||||
- wget
|
||||
- wl
|
||||
- xxd
|
||||
- zlib1g-dev
|
||||
environment:
|
||||
ARCHFLAGS: "-arch aarch64"
|
||||
|
||||
tasks:
|
||||
# First we install packages
|
||||
- name: install packages
|
||||
apt:
|
||||
name: "{{ packages.apt.install }}"
|
||||
state: present
|
||||
update_cache: yes
|
||||
install_recommends: false
|
||||
|
||||
# Now we set up /boot/firmware
|
||||
- name: Create pi user
|
||||
copy:
|
||||
dest: /boot/firmware/userconf
|
||||
content: |
|
||||
pi:$6$3jNr0GA9KIyt4hmM$efeVIopdMQ8DGgEPCWWlbx3mJJNAYci1lEXGdlky0xPyjqwKNbwTL5SrCcpb4144C4IvzWjn7Iv.QjqmU7iyT/
|
||||
|
||||
- name: enable ssh on boot
|
||||
file:
|
||||
path: /boot/firmware/ssh
|
||||
state: touch
|
||||
|
||||
- name: adjust /boot/firmware/config.txt
|
||||
lineinfile:
|
||||
dest: /boot/firmware/config.txt
|
||||
insertafter: EOF
|
||||
line: '{{ item }}'
|
||||
with_items: "{{ system.boot_options }}"
|
||||
|
||||
- name: change root partition
|
||||
replace:
|
||||
dest: /boot/firmware/cmdline.txt
|
||||
backup: no
|
||||
regexp: "root=PARTUUID=[a-zA-Z0-9\\-]+"
|
||||
replace: "root=/dev/mmcblk0p2"
|
||||
|
||||
- name: configure /boot/firmware/cmdline.txt
|
||||
lineinfile:
|
||||
path: /boot/firmware/cmdline.txt
|
||||
backrefs: True
|
||||
state: present
|
||||
backup: no
|
||||
regexp: '(.*)$'
|
||||
line: '\1 modules-load=dwc2,g_ether'
|
||||
|
||||
- name: change hostname
|
||||
lineinfile:
|
||||
dest: /etc/hostname
|
||||
regexp: '^raspberrypi'
|
||||
line: "{{pwnagotchi.hostname}}"
|
||||
state: present
|
||||
when: lookup('file', '/etc/hostname') == "raspberrypi"
|
||||
register: hostname
|
||||
|
||||
- name: add hostname to /etc/hosts
|
||||
lineinfile:
|
||||
dest: /etc/hosts
|
||||
regexp: '^127\.0\.1\.1[ \t]+raspberrypi'
|
||||
line: "127.0.1.1\t{{pwnagotchi.hostname}}"
|
||||
state: present
|
||||
when: hostname.changed
|
||||
|
||||
# Now we disable sap and a2dp, we don't use them on rpi
|
||||
- name: disable sap plugin for bluetooth.service
|
||||
lineinfile:
|
||||
dest: /lib/systemd/system/bluetooth.service
|
||||
regexp: '^ExecStart=/usr/libexec/bluetooth/bluetoothd$'
|
||||
line: 'ExecStart=/usr/libexec/bluetooth/bluetoothd --noplugin=sap,a2dp'
|
||||
state: present
|
||||
|
||||
###########################################
|
||||
#
|
||||
# libpcap v1.9 - build from source
|
||||
#
|
||||
###########################################
|
||||
|
||||
# check for presence, then it can re-run in later parts if needed
|
||||
# use the "make" built in
|
||||
|
||||
# install libpcap before bettercap and pwngrid, so they use it
|
||||
- name: clone libpcap v1.9 from github
|
||||
git:
|
||||
repo: 'https://github.com/the-tcpdump-group/libpcap.git'
|
||||
dest: /usr/local/src/libpcap
|
||||
version: libpcap-1.9
|
||||
|
||||
- name: build and install libpcap into /usr/local/lib
|
||||
shell: "./configure && make && make install"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/libpcap
|
||||
|
||||
- name: remove libpcap build folder
|
||||
file:
|
||||
state: absent
|
||||
path: /usr/local/src/libpcap
|
||||
|
||||
- name: create symlink /usr/local/lib/libpcap.so.1.9.1
|
||||
file:
|
||||
src: /usr/local/lib/libpcap.so.1.9.1
|
||||
dest: /usr/local/lib/libpcap.so.0.8
|
||||
state: link
|
||||
|
||||
# install latest hcxtools
|
||||
|
||||
- name: clone hcxtools
|
||||
git:
|
||||
repo: https://github.com/ZerBea/hcxtools.git
|
||||
dest: /usr/local/src/hcxtools
|
||||
|
||||
- name: install hcxtools
|
||||
shell: "make && make install"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/hcxtools
|
||||
|
||||
- name: remove hcxtools directory
|
||||
file:
|
||||
state: absent
|
||||
path: /usr/local/src/hcxtools
|
||||
|
||||
- name: clone nexmon repository
|
||||
git:
|
||||
repo: https://github.com/DrSchottky/nexmon.git
|
||||
dest: /usr/local/src/nexmon
|
||||
|
||||
# FIRST WE BUILD DRIVER FOR RPi5
|
||||
|
||||
- name: make firmware, RPi5
|
||||
shell: "source ./setup_env.sh && make"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/nexmon/
|
||||
environment:
|
||||
QEMU_UNAME: "{{ kernel.full_pi5 }}"
|
||||
ARCHFLAGS: "-arch aarch64"
|
||||
|
||||
- name: make firmware patch (bcm43455c0), RPi5
|
||||
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/
|
||||
environment:
|
||||
QEMU_UNAME: "{{ kernel.full_pi5 }}"
|
||||
ARCHFLAGS: "-arch aarch64"
|
||||
|
||||
- name: copy modified driver, RPi5
|
||||
copy:
|
||||
src: "/usr/local/src/nexmon/patches/driver/brcmfmac_{{ kernel.min }}.y-nexmon/brcmfmac.ko"
|
||||
dest: "/usr/lib/modules/{{ kernel.full_pi5 }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko"
|
||||
environment:
|
||||
QEMU_UNAME: "{{ kernel.full_pi5 }}"
|
||||
ARCHFLAGS: "-arch aarch64"
|
||||
|
||||
- name: Delete the modified driver, RPi5
|
||||
file:
|
||||
state: absent
|
||||
path: '/usr/local/src/nexmon/patches/driver/brcmfmac_{{ kernel.min }}.y-nexmon/brcmfmac.ko'
|
||||
|
||||
- name: backup original driver, RPi5
|
||||
command: "mv /usr/lib/modules/{{ kernel.full_pi5 }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz /usr/lib/modules/{{ kernel.full_pi5 }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz.orig"
|
||||
|
||||
- name: load brcmfmac drivers
|
||||
command: "/sbin/depmod {{ kernel.full_pi5 }}"
|
||||
environment:
|
||||
QEMU_UNAME: "{{ kernel.full_pi5 }}"
|
||||
|
||||
- name: Delete nexmon content & directory
|
||||
file:
|
||||
state: absent
|
||||
path: /usr/local/src/nexmon/
|
||||
|
||||
# NOW WE BUILD DRIVERS FOR RPi4, RPizero2w and RPi3
|
||||
|
||||
- name: clone nexmon repository
|
||||
git:
|
||||
repo: https://github.com/DrSchottky/nexmon.git
|
||||
dest: /usr/local/src/nexmon
|
||||
|
||||
- name: make firmware, RPi4
|
||||
shell: "source ./setup_env.sh && make"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/nexmon/
|
||||
environment:
|
||||
QEMU_UNAME: "{{ kernel.full }}"
|
||||
ARCHFLAGS: "-arch aarch64"
|
||||
|
||||
- name: make firmware patch (bcm43455c0), RPi4
|
||||
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/
|
||||
environment:
|
||||
QEMU_UNAME: "{{ kernel.full }}"
|
||||
ARCHFLAGS: "-arch aarch64"
|
||||
|
||||
- name: install new firmware (bcm43455c0), RPi4 RPi5
|
||||
copy:
|
||||
src: /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/brcmfmac43455-sdio.bin
|
||||
dest: /usr/lib/firmware/brcm/brcmfmac43455-sdio.bin
|
||||
follow: true
|
||||
|
||||
# NOW WE BUILD DRIVERS FOR RPiZero2W, RPi 3
|
||||
|
||||
- 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/
|
||||
environment:
|
||||
QEMU_UNAME: "{{ kernel.full }}"
|
||||
ARCHFLAGS: "-arch aarch64"
|
||||
|
||||
- name: install new firmware (bcm43436b0)
|
||||
copy:
|
||||
src: /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/brcmfmac43436-sdio.bin
|
||||
dest: /usr/lib/firmware/brcm/brcmfmac43436-sdio.bin
|
||||
follow: true
|
||||
|
||||
- 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/
|
||||
environment:
|
||||
QEMU_UNAME: "{{ kernel.full }}"
|
||||
ARCHFLAGS: "-arch aarch64"
|
||||
|
||||
- name: copy modified driver, RPi4
|
||||
copy:
|
||||
src: "/usr/local/src/nexmon/patches/driver/brcmfmac_{{ kernel.min }}.y-nexmon/brcmfmac.ko"
|
||||
dest: "/usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko"
|
||||
environment:
|
||||
QEMU_UNAME: "{{ kernel.full }}"
|
||||
ARCHFLAGS: "-arch aarch64"
|
||||
|
||||
- name: install new firmware (bcm43430a1)
|
||||
copy:
|
||||
src: /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/brcmfmac43430-sdio.bin
|
||||
dest: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin
|
||||
follow: true
|
||||
|
||||
- name: copy 43430-sdio as 43436s-sdio for the special 43430/1 /2
|
||||
copy:
|
||||
src: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin
|
||||
dest: /usr/lib/firmware/brcm/brcmfmac43436s-sdio.bin
|
||||
follow: true
|
||||
|
||||
# delete blob files that make nexmon sad
|
||||
- name: Delete the firmware blob files to avoid some nexmon crashing
|
||||
file:
|
||||
state: absent
|
||||
path: '{{ item }}'
|
||||
loop:
|
||||
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.clm_blob
|
||||
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,model-zero-w.clm_blob
|
||||
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,3-model-b.clm_blob
|
||||
- /usr/lib/firmware/brcm/brcmfmac43430b0-sdio.raspberrypi,model-zero-2-w.clm_blob
|
||||
- /usr/lib/firmware/brcm/brcmfmac43436-sdio.clm_blob
|
||||
- /usr/lib/firmware/brcm/brcmfmac43436-sdio.raspberrypi,model-zero-2-w.clm_blob
|
||||
- /usr/lib/firmware/brcm/brcmfmac43455-sdio.clm_blob
|
||||
|
||||
- name: backup original driver
|
||||
command: "mv /usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz /usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz.orig"
|
||||
|
||||
- name: load brcmfmac drivers
|
||||
command: "/sbin/depmod {{ kernel.full }}"
|
||||
environment:
|
||||
QEMU_UNAME: "{{ kernel.full }}"
|
||||
|
||||
# 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: Create custom plugin directory
|
||||
file:
|
||||
path: /usr/local/share/pwnagotchi/custom-plugins/
|
||||
state: directory
|
||||
|
||||
- name: Create custom config directory
|
||||
file:
|
||||
path: /etc/pwnagotchi/conf.d/
|
||||
state: directory
|
||||
|
||||
- name: clone pwnagotchi repository
|
||||
git:
|
||||
repo: https://github.com/jayofelony/pwnagotchi.git
|
||||
dest: /usr/local/src/pwnagotchi
|
||||
|
||||
- name: build pwnagotchi wheel
|
||||
command: "pip3 install . --no-cache-dir --break-system-packages"
|
||||
args:
|
||||
chdir: /usr/local/src/pwnagotchi
|
||||
|
||||
- name: remove pwnagotchi folder
|
||||
file:
|
||||
state: absent
|
||||
path: /usr/local/src/pwnagotchi
|
||||
|
||||
- name: create /usr/local/share/pwnagotchi/ folder
|
||||
file:
|
||||
path: /usr/local/share/pwnagotchi/
|
||||
state: directory
|
||||
|
||||
- name: Install go-1.21
|
||||
unarchive:
|
||||
src: https://go.dev/dl/go1.21.5.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
|
||||
when: golang.changed
|
||||
|
||||
- name: download pwngrid
|
||||
git:
|
||||
repo: "{{ packages.pwngrid.source }}"
|
||||
dest: /usr/local/src/pwngrid
|
||||
|
||||
- name: install pwngrid
|
||||
shell: "export GOPATH=$HOME/go && export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin && go mod tidy && make && make install"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/pwngrid
|
||||
|
||||
- name: remove pwngrid folder
|
||||
file:
|
||||
state: absent
|
||||
path: /usr/local/src/pwngrid
|
||||
|
||||
- name: download bettercap
|
||||
git:
|
||||
repo: "{{ packages.bettercap.source }}"
|
||||
dest: /usr/local/src/bettercap
|
||||
|
||||
- name: install bettercap 2.32.4
|
||||
shell: "export GOPATH=$HOME/go && export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin && go mod tidy && make && make install"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/bettercap
|
||||
|
||||
- name: remove bettercap folder
|
||||
file:
|
||||
state: absent
|
||||
path: /usr/local/src/bettercap
|
||||
|
||||
#- name: download and install bettercap
|
||||
# unarchive:
|
||||
# src: "{{ packages.bettercap.url }}"
|
||||
# dest: /usr/local/bin
|
||||
# remote_src: yes
|
||||
# exclude:
|
||||
# - README.md
|
||||
# - LICENSE.md
|
||||
# mode: 0755
|
||||
|
||||
- name: clone bettercap caplets
|
||||
git:
|
||||
repo: "{{ packages.caplets.source }}"
|
||||
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:
|
||||
src: "{{ packages.bettercap.ui }}"
|
||||
dest: /usr/local/share/bettercap/
|
||||
remote_src: yes
|
||||
mode: 0755
|
||||
|
||||
# to always have the bettercap webui available (because why not?)
|
||||
- name: copy pwnagotchi-manual over pwnagotchi-auto caplet
|
||||
ansible.builtin.copy:
|
||||
src: /usr/local/share/bettercap/caplets/pwnagotchi-manual.cap
|
||||
dest: /usr/local/share/bettercap/caplets/pwnagotchi-auto.cap
|
||||
force: true
|
||||
ignore_errors: true
|
||||
|
||||
- name: create /etc/pwnagotchi folder
|
||||
file:
|
||||
path: /etc/pwnagotchi
|
||||
state: directory
|
||||
|
||||
- name: check if user configuration exists
|
||||
stat:
|
||||
path: /etc/pwnagotchi/config.toml
|
||||
register: user_config
|
||||
|
||||
- name: create /etc/pwnagotchi/config.toml
|
||||
copy:
|
||||
dest: /etc/pwnagotchi/config.toml
|
||||
content: |
|
||||
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
|
||||
# Example:
|
||||
# ui.display.enabled = true
|
||||
# ui.display.type = "waveshare_2"
|
||||
when: not user_config.stat.exists
|
||||
|
||||
- name: Delete motd
|
||||
file:
|
||||
state: absent
|
||||
path: /etc/motd
|
||||
|
||||
- name: Delete motd 10-uname
|
||||
file:
|
||||
state: absent
|
||||
path: /etc/update-motd.d/10-uname
|
||||
|
||||
- name: Add pwnlog alias
|
||||
lineinfile:
|
||||
dest: /home/pi/.bashrc
|
||||
line: "\nalias pwnlog='tail -f -n300 /var/log/pwn*.log | sed --unbuffered \"s/,[[:digit:]]\\{3\\}\\]//g\" | cut -d \" \" -f 2-'"
|
||||
insertafter: EOF
|
||||
|
||||
- name: Add pwnver alias
|
||||
lineinfile:
|
||||
dest: /home/pi/.bashrc
|
||||
line: "\nalias pwnver='python3 -c \"import pwnagotchi as p; print(p.__version__)\"'"
|
||||
insertafter: EOF
|
||||
|
||||
- name: Add pwnkill alias to restart pwnagotchi with a signal
|
||||
lineinfile:
|
||||
dest: /home/pi/.bashrc
|
||||
line: "\nalias pwnkill='sudo killall -USR1 pwnagotchi'"
|
||||
insertafter: EOF
|
||||
|
||||
- name: add firmware packages to hold
|
||||
dpkg_selections:
|
||||
name: "{{ item }}"
|
||||
selection: hold
|
||||
with_items: "{{ packages.apt.hold }}"
|
||||
|
||||
- name: disable unnecessary services
|
||||
systemd:
|
||||
name: "{{ item }}"
|
||||
state: stopped
|
||||
enabled: no
|
||||
with_items: "{{ services.disable }}"
|
||||
|
||||
- name: enable services
|
||||
systemd:
|
||||
name: "{{ item }}"
|
||||
enabled: true
|
||||
state: stopped
|
||||
with_items: "{{ services.enable }}"
|
||||
register: enabled
|
||||
|
||||
- name: make /root readable, becauase that's where all the files are
|
||||
file:
|
||||
path: /root
|
||||
mode: '755'
|
||||
|
||||
- name: fix permissions on /home/pi
|
||||
file:
|
||||
path: /home/pi
|
||||
owner: pi
|
||||
group: pi
|
||||
recurse: true
|
||||
|
||||
- name: remove pre-collected packages zip
|
||||
file:
|
||||
path: /root/go_pkgs.tgz
|
||||
state: absent
|
||||
|
||||
- name: remove /root/go folder
|
||||
file:
|
||||
state: absent
|
||||
path: /root/go
|
||||
|
||||
- name: remove /usr/local/go folder
|
||||
file:
|
||||
state: absent
|
||||
path: /usr/local/go
|
||||
|
||||
- name: remove pip cache
|
||||
file:
|
||||
state: absent
|
||||
path: /root/.cache/pip
|
||||
|
||||
- name: remove ssh keys
|
||||
file:
|
||||
state: absent
|
||||
path: "{{ item }}"
|
||||
with_fileglob:
|
||||
- "/etc/ssh/ssh_host*_key*"
|
||||
|
||||
- name: regenerate ssh keys
|
||||
shell: "dpkg-reconfigure openssh-server"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
|
||||
# Now we remove packages
|
||||
- name: remove unnecessary apt packages
|
||||
apt:
|
||||
name: "{{ packages.apt.remove }}"
|
||||
state: absent
|
||||
purge: yes
|
||||
register: removed
|
||||
|
||||
- name: remove dependencies that are no longer required
|
||||
apt:
|
||||
autoremove: yes
|
||||
when: removed.changed
|
||||
|
||||
- name: clean apt cache
|
||||
apt:
|
||||
autoclean: true
|
||||
when: removed.changed
|
||||
|
||||
handlers:
|
||||
- name: reload systemd services
|
||||
systemd:
|
||||
daemon_reload: yes
|
||||
when: enabled.changed
|
0
builder/data/64bit/root/client_secrets.json
Normal file
0
builder/data/64bit/root/client_secrets.json
Normal file
15
builder/data/64bit/root/settings.yaml
Normal file
15
builder/data/64bit/root/settings.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
client_config_backend: file
|
||||
client_config_file: /root/client_secrets.json
|
||||
client_config:
|
||||
client_id: <YOUR CLIENT ID>
|
||||
client_secret: <YOUR CLIENT SECRET>
|
||||
|
||||
save_credentials: True
|
||||
save_credentials_backend: file
|
||||
save_credentials_file: /root/credentials.json
|
||||
|
||||
get_refresh_token: True
|
||||
|
||||
oauth_scope:
|
||||
- https://www.googleapis.com/auth/drive
|
||||
- https://www.googleapis.com/auth/drive.install
|
19
builder/data/64bit/usr/bin/bettercap-launcher
Executable file
19
builder/data/64bit/usr/bin/bettercap-launcher
Executable file
@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
source /usr/bin/pwnlib
|
||||
|
||||
# we need to decrypt something
|
||||
if is_crypted_mode; then
|
||||
while ! is_decrypted; do
|
||||
echo "Waiting for decryption..."
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
|
||||
# start mon0
|
||||
start_monitor_interface
|
||||
|
||||
if is_auto_mode_no_delete; then
|
||||
/usr/local/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface wlan0mon
|
||||
else
|
||||
/usr/local/bin/bettercap -no-colors -caplet pwnagotchi-manual -iface wlan0mon
|
||||
fi
|
148
builder/data/64bit/usr/bin/decryption-webserver
Executable file
148
builder/data/64bit/usr/bin/decryption-webserver
Executable file
@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from urllib.parse import parse_qsl
|
||||
|
||||
|
||||
_HTML_FORM_TEMPLATE = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Decryption</title>
|
||||
<style>
|
||||
body {{ text-align: center; padding: 150px; }}
|
||||
h1 {{ font-size: 50px; }}
|
||||
body {{ font: 20px Helvetica, sans-serif; color: #333; }}
|
||||
article {{ display: block; text-align: center; width: 650px; margin: 0 auto;}}
|
||||
input {{
|
||||
padding: 12px 20px;
|
||||
margin: 8px 0;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #ccc;
|
||||
}}
|
||||
input[type=password] {{
|
||||
width: 75%;
|
||||
font-size: 24px;
|
||||
}}
|
||||
input[type=submit] {{
|
||||
cursor: pointer;
|
||||
width: 75%;
|
||||
}}
|
||||
input[type=submit]:hover {{
|
||||
background-color: #d9d9d9;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<article>
|
||||
<h1>Decryption</h1>
|
||||
<p>Some of your files are encrypted.</p>
|
||||
<p>Please provide the decryption password.</p>
|
||||
<div>
|
||||
<form action="/set-password" method="POST">
|
||||
{password_fields}
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
POST_RESPONSE = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
/* Center the loader */
|
||||
#loader {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
z-index: 1;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
margin: -75px 0 0 -75px;
|
||||
border: 16px solid #f3f3f3;
|
||||
border-radius: 50%;
|
||||
border-top: 16px solid #3498db;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
-webkit-animation: spin 2s linear infinite;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
0% { -webkit-transform: rotate(0deg); }
|
||||
100% { -webkit-transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
#myDiv {
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
|
||||
function checkPwnagotchi() {
|
||||
var target = 'http://' + document.location.hostname + ':8080/';
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', target);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState == 4) {
|
||||
if (xhr.status == 200 || xhr.status == 401) {
|
||||
window.location.replace(target);
|
||||
}else{
|
||||
setTimeout(checkPwnagotchi, 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
setTimeout(checkPwnagotchi, 1000);
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body style="margin:0;">
|
||||
|
||||
<div id="loader"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
HTML_FORM = None
|
||||
|
||||
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
|
||||
|
||||
def do_GET(self):
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(HTML_FORM.encode())
|
||||
|
||||
def do_POST(self):
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
body = self.rfile.read(content_length)
|
||||
for mapping, password in parse_qsl(body.decode('UTF-8')):
|
||||
with open('/tmp/.pwnagotchi-secret-{}'.format(mapping), 'wt') as pwfile:
|
||||
pwfile.write(password)
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(POST_RESPONSE.encode())
|
||||
|
||||
|
||||
with open('/root/.pwnagotchi-crypted') as crypted_file:
|
||||
mappings = [line.split()[0] for line in crypted_file.readlines()]
|
||||
fields = ''.join(['<label for="{m}">Passphrase for {m}:</label>\n<input type="password" id="{m}" name="{m}" value=""><br>'.format(m=m)
|
||||
for m in mappings])
|
||||
HTML_FORM = _HTML_FORM_TEMPLATE.format(password_fields=fields)
|
||||
|
||||
httpd = HTTPServer(('0.0.0.0', 80), SimpleHTTPRequestHandler)
|
||||
httpd.serve_forever()
|
2
builder/data/64bit/usr/bin/hdmioff
Executable file
2
builder/data/64bit/usr/bin/hdmioff
Executable file
@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
sudo /usr/bin/tvservice -o
|
2
builder/data/64bit/usr/bin/hdmion
Executable file
2
builder/data/64bit/usr/bin/hdmion
Executable file
@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
sudo /usr/bin/tvservice -p
|
3
builder/data/64bit/usr/bin/monstart
Executable file
3
builder/data/64bit/usr/bin/monstart
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
source /usr/bin/pwnlib
|
||||
start_monitor_interface
|
3
builder/data/64bit/usr/bin/monstop
Executable file
3
builder/data/64bit/usr/bin/monstop
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
source /usr/bin/pwnlib
|
||||
stop_monitor_interface
|
16
builder/data/64bit/usr/bin/pwnagotchi-launcher
Executable file
16
builder/data/64bit/usr/bin/pwnagotchi-launcher
Executable file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
source /usr/bin/pwnlib
|
||||
|
||||
# we need to decrypt something
|
||||
if is_crypted_mode; then
|
||||
while ! is_decrypted; do
|
||||
echo "Waiting for decryption..."
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
|
||||
if is_auto_mode; then
|
||||
/usr/local/bin/pwnagotchi
|
||||
else
|
||||
/usr/local/bin/pwnagotchi --manual
|
||||
fi
|
191
builder/data/64bit/usr/bin/pwnlib
Executable file
191
builder/data/64bit/usr/bin/pwnlib
Executable file
@ -0,0 +1,191 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# well ... it blinks the led
|
||||
blink_led() {
|
||||
# shellcheck disable=SC2034
|
||||
for i in $(seq 1 "$1"); do
|
||||
echo 0 >/sys/class/leds/led0/brightness
|
||||
sleep 0.3
|
||||
echo 1 >/sys/class/leds/led0/brightness
|
||||
sleep 0.3
|
||||
done
|
||||
echo 0 >/sys/class/leds/led0/brightness
|
||||
sleep 0.3
|
||||
}
|
||||
|
||||
# reload mod
|
||||
reload_brcm() {
|
||||
if ! modprobe -r brcmfmac; then
|
||||
return 1
|
||||
fi
|
||||
sleep 1
|
||||
if ! modprobe brcmfmac; then
|
||||
return 1
|
||||
fi
|
||||
sleep 2
|
||||
iw dev wlan0 set power_save off
|
||||
return 0
|
||||
}
|
||||
|
||||
# starts mon0
|
||||
start_monitor_interface() {
|
||||
rfkill unblock all
|
||||
ifconfig wlan0 up
|
||||
sleep 3
|
||||
iw dev wlan0 set power_save off
|
||||
iw phy "$(iw phy | head -1 | cut -d" " -f2)" interface add wlan0mon type monitor
|
||||
sleep 2
|
||||
rfkill unblock all
|
||||
ifconfig wlan0 down
|
||||
ifconfig wlan0mon up
|
||||
iw dev wlan0mon set power_save off
|
||||
}
|
||||
|
||||
# stops mon0
|
||||
stop_monitor_interface() {
|
||||
ifconfig wlan0mon down && iw dev wlan0mon del
|
||||
reload_brcm
|
||||
ifconfig wlan0 up
|
||||
}
|
||||
|
||||
# returns 0 if the specified network interface is up
|
||||
is_interface_up() {
|
||||
if grep -qi 'up' /sys/class/net/"$1"/operstate; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# returns 0 if conditions for AUTO mode are met
|
||||
is_auto_mode() {
|
||||
# check override file first
|
||||
if [ -f /root/.pwnagotchi-manual ]; then
|
||||
# remove the override file if found
|
||||
rm -rf /root/.pwnagotchi-manual
|
||||
return 1
|
||||
fi
|
||||
|
||||
# check override file first
|
||||
if [ -f /root/.pwnagotchi-auto ]; then
|
||||
# remove the override file if found
|
||||
rm -rf /root/.pwnagotchi-auto
|
||||
return 0
|
||||
fi
|
||||
|
||||
# if usb0 is up, we're in MANU
|
||||
if is_interface_up usb0; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# if eth0 is up (for other boards), we're in MANU
|
||||
if is_interface_up eth0; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# no override, but none of the interfaces is up -> AUTO
|
||||
return 0
|
||||
}
|
||||
|
||||
# returns 0 if conditions for AUTO mode are met
|
||||
is_auto_mode_no_delete() {
|
||||
# check override file first
|
||||
if [ -f /root/.pwnagotchi-manual ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# check override file first
|
||||
if [ -f /root/.pwnagotchi-auto ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# if usb0 is up, we're in MANU
|
||||
if is_interface_up usb0; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# if eth0 is up (for other boards), we're in MANU
|
||||
if is_interface_up eth0; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# no override, but none of the interfaces is up -> AUTO
|
||||
return 0
|
||||
}
|
||||
|
||||
# check if we need to decrypt something
|
||||
is_crypted_mode() {
|
||||
if [ -f /root/.pwnagotchi-crypted ]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# decryption loop
|
||||
is_decrypted() {
|
||||
while read -r mapping container mount; do
|
||||
# mapping = name the device or file will be mapped to
|
||||
# container = the luks encrypted device or file
|
||||
# mount = the mountpoint
|
||||
|
||||
# fail if not mounted
|
||||
if ! mountpoint -q "$mount" >/dev/null 2>&1; then
|
||||
if [ -f /tmp/.pwnagotchi-secret-"$mapping" ]; then
|
||||
</tmp/.pwnagotchi-secret-"$mapping" read -r SECRET
|
||||
if ! test -b /dev/disk/by-id/dm-uuid-*"$(cryptsetup luksUUID "$container" | tr -d -)"*; then
|
||||
if echo -n "$SECRET" | cryptsetup luksOpen -d- "$container" "$mapping" >/dev/null 2>&1; then
|
||||
echo "Container decrypted!"
|
||||
fi
|
||||
fi
|
||||
|
||||
if mount /dev/mapper/"$mapping" "$mount" >/dev/null 2>&1; then
|
||||
echo "Mounted /dev/mapper/$mapping to $mount"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! ip -4 addr show wlan0 | grep inet >/dev/null 2>&1; then
|
||||
>/dev/null 2>&1 ip addr add 192.168.0.10/24 dev wlan0
|
||||
fi
|
||||
|
||||
if ! pgrep -f decryption-webserver >/dev/null 2>&1; then
|
||||
>/dev/null 2>&1 decryption-webserver &
|
||||
fi
|
||||
|
||||
if ! pgrep wpa_supplicant >/dev/null 2>&1; then
|
||||
>/tmp/wpa_supplicant.conf cat <<EOF
|
||||
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
|
||||
update_config=1
|
||||
ap_scan=2
|
||||
|
||||
network={
|
||||
ssid="DECRYPT-ME"
|
||||
mode=2
|
||||
key_mgmt=WPA-PSK
|
||||
psk="pwnagotchi"
|
||||
frequency=2437
|
||||
}
|
||||
EOF
|
||||
>/dev/null 2>&1 wpa_supplicant -u -s -O -D nl80211 -i wlan0 -c /tmp/wpa_supplicant.conf &
|
||||
fi
|
||||
|
||||
if ! pgrep dnsmasq >/dev/null 2>&1; then
|
||||
>/dev/null 2>&1 dnsmasq -k -p 53 -h -O "6,192.168.0.10" -A "/#/192.168.0.10" -i wlan0 -K -F 192.168.0.50,192.168.0.60,255.255.255.0,24h &
|
||||
fi
|
||||
|
||||
return 1
|
||||
fi
|
||||
done </root/.pwnagotchi-crypted
|
||||
|
||||
# overwrite passwords
|
||||
python3 -c 'print("A"*4096)' | tee /tmp/.pwnagotchi-secret-* >/dev/null
|
||||
# delete
|
||||
rm /tmp/.pwnagotchi-secret-*
|
||||
sync # flush
|
||||
|
||||
pkill wpa_supplicant
|
||||
pkill dnsmasq
|
||||
pid="$(pgrep -f "decryption-webserver")"
|
||||
[[ -n "$pid" ]] && kill "$pid"
|
||||
|
||||
return 0
|
||||
}
|
168
pwnagotchi/__init__.py
Normal file
168
pwnagotchi/__init__.py
Normal file
@ -0,0 +1,168 @@
|
||||
import os
|
||||
import logging
|
||||
import time
|
||||
import re
|
||||
|
||||
from pwnagotchi._version import __version__
|
||||
|
||||
_name = None
|
||||
config = None
|
||||
_cpu_stats = {}
|
||||
|
||||
|
||||
def set_name(new_name):
|
||||
if new_name is None:
|
||||
return
|
||||
|
||||
new_name = new_name.strip()
|
||||
if new_name == '':
|
||||
return
|
||||
|
||||
if not re.match(r'^[a-zA-Z0-9\-]{2,25}$', new_name):
|
||||
logging.warning("name '%s' is invalid: min length is 2, max length 25, only a-zA-Z0-9- allowed", new_name)
|
||||
return
|
||||
|
||||
current = name()
|
||||
if new_name != current:
|
||||
global _name
|
||||
|
||||
logging.info("setting unit hostname '%s' -> '%s'", current, new_name)
|
||||
with open('/etc/hostname', 'wt') as fp:
|
||||
fp.write(new_name)
|
||||
|
||||
with open('/etc/hosts', 'rt') as fp:
|
||||
prev = fp.read()
|
||||
logging.debug("old hosts:\n%s\n", prev)
|
||||
|
||||
with open('/etc/hosts', 'wt') as fp:
|
||||
patched = prev.replace(current, new_name, -1)
|
||||
logging.debug("new hosts:\n%s\n", patched)
|
||||
fp.write(patched)
|
||||
|
||||
os.system("hostname '%s'" % new_name)
|
||||
reboot()
|
||||
|
||||
|
||||
def name():
|
||||
global _name
|
||||
if _name is None:
|
||||
with open('/etc/hostname', 'rt') as fp:
|
||||
_name = fp.read().strip()
|
||||
return _name
|
||||
|
||||
|
||||
def uptime():
|
||||
with open('/proc/uptime') as fp:
|
||||
return int(fp.read().split('.')[0])
|
||||
|
||||
|
||||
def mem_usage():
|
||||
with open('/proc/meminfo') as fp:
|
||||
for line in fp:
|
||||
line = line.strip()
|
||||
if line.startswith("MemTotal:"):
|
||||
kb_mem_total = int(line.split()[1])
|
||||
if line.startswith("MemFree:"):
|
||||
kb_mem_free = int(line.split()[1])
|
||||
if line.startswith("Buffers:"):
|
||||
kb_main_buffers = int(line.split()[1])
|
||||
if line.startswith("Cached:"):
|
||||
kb_main_cached = int(line.split()[1])
|
||||
kb_mem_used = kb_mem_total - kb_mem_free - kb_main_cached - kb_main_buffers
|
||||
return round(kb_mem_used / kb_mem_total, 1)
|
||||
|
||||
|
||||
def _cpu_stat():
|
||||
"""
|
||||
Returns the split first line of the /proc/stat file
|
||||
"""
|
||||
with open('/proc/stat', 'rt') as fp:
|
||||
return list(map(int, fp.readline().split()[1:]))
|
||||
|
||||
|
||||
def cpu_load(tag=None):
|
||||
"""
|
||||
Returns the current cpuload
|
||||
"""
|
||||
if tag and tag in _cpu_stats.keys():
|
||||
parts0 = _cpu_stats[tag]
|
||||
else:
|
||||
parts0 = _cpu_stat()
|
||||
time.sleep(0.1) # only need to sleep when no tag
|
||||
parts1 = _cpu_stat()
|
||||
if tag:
|
||||
_cpu_stats[tag] = parts1
|
||||
|
||||
parts_diff = [p1 - p0 for (p0, p1) in zip(parts0, parts1)]
|
||||
user, nice, sys, idle, iowait, irq, softirq, steal, _guest, _guest_nice = parts_diff
|
||||
idle_sum = idle + iowait
|
||||
non_idle_sum = user + nice + sys + irq + softirq + steal
|
||||
total = idle_sum + non_idle_sum
|
||||
return non_idle_sum / total
|
||||
|
||||
|
||||
def temperature(celsius=True):
|
||||
with open('/sys/class/thermal/thermal_zone0/temp', 'rt') as fp:
|
||||
temp = int(fp.read().strip())
|
||||
c = int(temp / 1000)
|
||||
return c if celsius else ((c * (9 / 5)) + 32)
|
||||
|
||||
|
||||
def shutdown():
|
||||
logging.warning("shutting down ...")
|
||||
|
||||
from pwnagotchi.ui import view
|
||||
if view.ROOT:
|
||||
view.ROOT.on_shutdown()
|
||||
# give it some time to refresh the ui
|
||||
time.sleep(10)
|
||||
|
||||
logging.warning("syncing...")
|
||||
|
||||
from pwnagotchi import fs
|
||||
for m in fs.mounts:
|
||||
m.sync()
|
||||
|
||||
os.system("sync")
|
||||
os.system("halt")
|
||||
|
||||
|
||||
def restart(mode):
|
||||
logging.warning("restarting in %s mode ...", mode)
|
||||
mode = mode.upper()
|
||||
if mode == 'AUTO':
|
||||
os.system("touch /root/.pwnagotchi-auto")
|
||||
else:
|
||||
os.system("touch /root/.pwnagotchi-manual")
|
||||
|
||||
os.system("service bettercap restart")
|
||||
time.sleep(1)
|
||||
os.system("service pwnagotchi restart")
|
||||
|
||||
|
||||
def reboot(mode=None):
|
||||
if mode is not None:
|
||||
mode = mode.upper()
|
||||
logging.warning("rebooting in %s mode ...", mode)
|
||||
else:
|
||||
logging.warning("rebooting ...")
|
||||
|
||||
from pwnagotchi.ui import view
|
||||
if view.ROOT:
|
||||
view.ROOT.on_rebooting()
|
||||
# give it some time to refresh the ui
|
||||
time.sleep(10)
|
||||
|
||||
if mode == 'AUTO':
|
||||
os.system("touch /root/.pwnagotchi-auto")
|
||||
elif mode == 'MANU':
|
||||
os.system("touch /root/.pwnagotchi-manual")
|
||||
|
||||
logging.warning("syncing...")
|
||||
|
||||
from pwnagotchi import fs
|
||||
for m in fs.mounts:
|
||||
m.sync()
|
||||
|
||||
os.system("sync")
|
||||
os.system("shutdown -r now")
|
1
pwnagotchi/_version.py
Normal file
1
pwnagotchi/_version.py
Normal file
@ -0,0 +1 @@
|
||||
__version__ = '2.8.4'
|
505
pwnagotchi/agent.py
Normal file
505
pwnagotchi/agent.py
Normal file
@ -0,0 +1,505 @@
|
||||
import time
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
import asyncio
|
||||
import _thread
|
||||
|
||||
import pwnagotchi
|
||||
import pwnagotchi.utils as utils
|
||||
import pwnagotchi.plugins as plugins
|
||||
from pwnagotchi.ui.web.server import Server
|
||||
from pwnagotchi.automata import Automata
|
||||
from pwnagotchi.log import LastSession
|
||||
from pwnagotchi.bettercap import Client
|
||||
from pwnagotchi.mesh.utils import AsyncAdvertiser
|
||||
from pwnagotchi.ai.train import AsyncTrainer
|
||||
|
||||
RECOVERY_DATA_FILE = '/root/.pwnagotchi-recovery'
|
||||
|
||||
|
||||
class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
def __init__(self, view, config, keypair):
|
||||
Client.__init__(self,
|
||||
"127.0.0.1" if "hostname" not in config['bettercap'] else config['bettercap']['hostname'],
|
||||
"http" if "scheme" not in config['bettercap'] else config['bettercap']['scheme'],
|
||||
8081 if "port" not in config['bettercap'] else config['bettercap']['port'],
|
||||
"pwnagotchi" if "username" not in config['bettercap'] else config['bettercap']['username'],
|
||||
"pwnagotchi" if "password" not in config['bettercap'] else config['bettercap']['password'])
|
||||
Automata.__init__(self, config, view)
|
||||
AsyncAdvertiser.__init__(self, config, view, keypair)
|
||||
AsyncTrainer.__init__(self, config)
|
||||
|
||||
self._started_at = time.time()
|
||||
self._current_channel = 0
|
||||
self._tot_aps = 0
|
||||
self._aps_on_channel = 0
|
||||
self._supported_channels = utils.iface_channels(config['main']['iface'])
|
||||
self._view = view
|
||||
self._view.set_agent(self)
|
||||
self._web_ui = Server(self, config['ui'])
|
||||
|
||||
self._access_points = []
|
||||
self._last_pwnd = None
|
||||
self._history = {}
|
||||
self._handshakes = {}
|
||||
self.last_session = LastSession(self._config)
|
||||
self.mode = 'auto'
|
||||
|
||||
if not os.path.exists(config['bettercap']['handshakes']):
|
||||
os.makedirs(config['bettercap']['handshakes'])
|
||||
|
||||
logging.info("%s@%s (v%s)", pwnagotchi.name(), self.fingerprint(), pwnagotchi.__version__)
|
||||
for _, plugin in plugins.loaded.items():
|
||||
logging.debug("plugin '%s' v%s", plugin.__class__.__name__, plugin.__version__)
|
||||
|
||||
def config(self):
|
||||
return self._config
|
||||
|
||||
def view(self):
|
||||
return self._view
|
||||
|
||||
def supported_channels(self):
|
||||
return self._supported_channels
|
||||
|
||||
def setup_events(self):
|
||||
logging.info("connecting to %s ...", self.url)
|
||||
|
||||
for tag in self._config['bettercap']['silence']:
|
||||
try:
|
||||
self.run('events.ignore %s' % tag, verbose_errors=False)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _reset_wifi_settings(self):
|
||||
mon_iface = self._config['main']['iface']
|
||||
self.run('set wifi.interface %s' % mon_iface)
|
||||
self.run('set wifi.ap.ttl %d' % self._config['personality']['ap_ttl'])
|
||||
self.run('set wifi.sta.ttl %d' % self._config['personality']['sta_ttl'])
|
||||
self.run('set wifi.rssi.min %d' % self._config['personality']['min_rssi'])
|
||||
self.run('set wifi.handshakes.file %s' % self._config['bettercap']['handshakes'])
|
||||
self.run('set wifi.handshakes.aggregate false')
|
||||
|
||||
def start_monitor_mode(self):
|
||||
mon_iface = self._config['main']['iface']
|
||||
mon_start_cmd = self._config['main']['mon_start_cmd']
|
||||
restart = not self._config['main']['no_restart']
|
||||
has_mon = False
|
||||
|
||||
while has_mon is False:
|
||||
s = self.session()
|
||||
for iface in s['interfaces']:
|
||||
if iface['name'] == mon_iface:
|
||||
logging.info("found monitor interface: %s", iface['name'])
|
||||
has_mon = True
|
||||
break
|
||||
|
||||
if has_mon is False:
|
||||
if mon_start_cmd is not None and mon_start_cmd != '':
|
||||
logging.info("starting monitor interface ...")
|
||||
self.run('!%s' % mon_start_cmd)
|
||||
else:
|
||||
logging.info("waiting for monitor interface %s ...", mon_iface)
|
||||
time.sleep(1)
|
||||
|
||||
logging.info("supported channels: %s", self._supported_channels)
|
||||
logging.info("handshakes will be collected inside %s", self._config['bettercap']['handshakes'])
|
||||
|
||||
self._reset_wifi_settings()
|
||||
|
||||
wifi_running = self.is_module_running('wifi')
|
||||
if wifi_running and restart:
|
||||
logging.debug("restarting wifi module ...")
|
||||
self.restart_module('wifi.recon')
|
||||
self.run('wifi.clear')
|
||||
elif not wifi_running:
|
||||
logging.debug("starting wifi module ...")
|
||||
self.start_module('wifi.recon')
|
||||
|
||||
self.start_advertising()
|
||||
|
||||
def _wait_bettercap(self):
|
||||
while True:
|
||||
try:
|
||||
_s = self.session()
|
||||
return
|
||||
except Exception:
|
||||
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()
|
||||
self.start_event_polling()
|
||||
self.start_session_fetcher()
|
||||
# print initial stats
|
||||
self.next_epoch()
|
||||
self.set_ready()
|
||||
|
||||
def recon(self):
|
||||
recon_time = self._config['personality']['recon_time']
|
||||
max_inactive = self._config['personality']['max_inactive_scale']
|
||||
recon_mul = self._config['personality']['recon_inactive_multiplier']
|
||||
channels = self._config['personality']['channels']
|
||||
|
||||
if self._epoch.inactive_for >= max_inactive:
|
||||
recon_time *= recon_mul
|
||||
|
||||
self._view.set('channel', '*')
|
||||
|
||||
if not channels:
|
||||
self._current_channel = 0
|
||||
logging.debug("RECON %ds", recon_time)
|
||||
self.run('wifi.recon.channel clear')
|
||||
else:
|
||||
logging.debug("RECON %ds ON CHANNELS %s", recon_time, ','.join(map(str, channels)))
|
||||
try:
|
||||
self.run('wifi.recon.channel %s' % ','.join(map(str, channels)))
|
||||
except Exception as e:
|
||||
logging.exception("Error while setting wifi.recon.channels (%s)", e)
|
||||
|
||||
self.wait_for(recon_time, sleeping=False)
|
||||
|
||||
def set_access_points(self, aps):
|
||||
self._access_points = aps
|
||||
plugins.on('wifi_update', self, aps)
|
||||
self._epoch.observe(aps, list(self._peers.values()))
|
||||
return self._access_points
|
||||
|
||||
def get_access_points(self):
|
||||
whitelist = self._config['main']['whitelist']
|
||||
aps = []
|
||||
try:
|
||||
s = self.session()
|
||||
plugins.on("unfiltered_ap_list", self, s['wifi']['aps'])
|
||||
for ap in s['wifi']['aps']:
|
||||
if ap['encryption'] == '' or ap['encryption'] == 'OPEN':
|
||||
continue
|
||||
elif ap['hostname'] in whitelist or ap['mac'][:13].lower() in whitelist or ap['mac'].lower() in whitelist:
|
||||
continue
|
||||
else:
|
||||
aps.append(ap)
|
||||
except Exception as e:
|
||||
logging.exception("Error while getting access points (%s)", e)
|
||||
|
||||
aps.sort(key=lambda ap: ap['channel'])
|
||||
return self.set_access_points(aps)
|
||||
|
||||
def get_total_aps(self):
|
||||
return self._tot_aps
|
||||
|
||||
def get_aps_on_channel(self):
|
||||
return self._aps_on_channel
|
||||
|
||||
def get_current_channel(self):
|
||||
return self._current_channel
|
||||
|
||||
def get_access_points_by_channel(self):
|
||||
aps = self.get_access_points()
|
||||
channels = self._config['personality']['channels']
|
||||
grouped = {}
|
||||
|
||||
# group by channel
|
||||
for ap in aps:
|
||||
ch = ap['channel']
|
||||
# if we're sticking to a channel, skip anything
|
||||
# which is not on that channel
|
||||
if channels and ch not in channels:
|
||||
continue
|
||||
|
||||
if ch not in grouped:
|
||||
grouped[ch] = [ap]
|
||||
else:
|
||||
grouped[ch].append(ap)
|
||||
|
||||
# sort by more populated channels
|
||||
return sorted(grouped.items(), key=lambda kv: len(kv[1]), reverse=True)
|
||||
|
||||
def _find_ap_sta_in(self, station_mac, ap_mac, session):
|
||||
for ap in session['wifi']['aps']:
|
||||
if ap['mac'] == ap_mac:
|
||||
for sta in ap['clients']:
|
||||
if sta['mac'] == station_mac:
|
||||
return ap, sta
|
||||
return ap, {'mac': station_mac, 'vendor': ''}
|
||||
return None
|
||||
|
||||
def _update_uptime(self, s):
|
||||
secs = pwnagotchi.uptime()
|
||||
self._view.set('uptime', utils.secs_to_hhmmss(secs))
|
||||
# self._view.set('epoch', '%04d' % self._epoch.epoch)
|
||||
|
||||
def _update_counters(self):
|
||||
self._tot_aps = len(self._access_points)
|
||||
tot_stas = sum(len(ap['clients']) for ap in self._access_points)
|
||||
if self._current_channel == 0:
|
||||
self._view.set('aps', '%d' % self._tot_aps)
|
||||
self._view.set('sta', '%d' % tot_stas)
|
||||
else:
|
||||
self._aps_on_channel = len([ap for ap in self._access_points if ap['channel'] == self._current_channel])
|
||||
stas_on_channel = sum(
|
||||
[len(ap['clients']) for ap in self._access_points if ap['channel'] == self._current_channel])
|
||||
self._view.set('aps', '%d (%d)' % (self._aps_on_channel, self._tot_aps))
|
||||
self._view.set('sta', '%d (%d)' % (stas_on_channel, tot_stas))
|
||||
|
||||
def _update_handshakes(self, new_shakes=0):
|
||||
if new_shakes > 0:
|
||||
self._epoch.track(handshake=True, inc=new_shakes)
|
||||
|
||||
tot = utils.total_unique_handshakes(self._config['bettercap']['handshakes'])
|
||||
txt = '%d (%d)' % (len(self._handshakes), tot)
|
||||
|
||||
if self._last_pwnd is not None:
|
||||
txt += ' [%s]' % self._last_pwnd[:11] # So it doesn't overlap with fix_brcmfmac_plugin
|
||||
|
||||
self._view.set('shakes', txt)
|
||||
|
||||
if new_shakes > 0:
|
||||
self._view.on_handshakes(new_shakes)
|
||||
|
||||
def _update_peers(self):
|
||||
self._view.set_closest_peer(self._closest_peer, len(self._peers))
|
||||
|
||||
def _reboot(self):
|
||||
self.set_rebooting()
|
||||
self._save_recovery_data()
|
||||
pwnagotchi.reboot()
|
||||
|
||||
def _restart(self):
|
||||
self._save_recovery_data()
|
||||
pwnagotchi.restart("AUTO")
|
||||
|
||||
def _save_recovery_data(self):
|
||||
logging.warning("writing recovery data to %s ...", RECOVERY_DATA_FILE)
|
||||
with open(RECOVERY_DATA_FILE, 'w') as fp:
|
||||
data = {
|
||||
'started_at': self._started_at,
|
||||
'epoch': self._epoch.epoch,
|
||||
'history': self._history,
|
||||
'handshakes': self._handshakes,
|
||||
'last_pwnd': self._last_pwnd
|
||||
}
|
||||
json.dump(data, fp)
|
||||
|
||||
def _load_recovery_data(self, delete=True, no_exceptions=True):
|
||||
try:
|
||||
with open(RECOVERY_DATA_FILE, 'rt') as fp:
|
||||
data = json.load(fp)
|
||||
logging.info("found recovery data: %s", data)
|
||||
self._started_at = data['started_at']
|
||||
self._epoch.epoch = data['epoch']
|
||||
self._handshakes = data['handshakes']
|
||||
self._history = data['history']
|
||||
self._last_pwnd = data['last_pwnd']
|
||||
|
||||
if delete:
|
||||
logging.info("deleting %s", RECOVERY_DATA_FILE)
|
||||
os.unlink(RECOVERY_DATA_FILE)
|
||||
except:
|
||||
if not no_exceptions:
|
||||
raise
|
||||
|
||||
def start_session_fetcher(self):
|
||||
_thread.start_new_thread(self._fetch_stats, ())
|
||||
|
||||
def _fetch_stats(self):
|
||||
while True:
|
||||
try:
|
||||
s = self.session()
|
||||
except Exception as err:
|
||||
logging.error("[agent:_fetch_stats] self.session: %s" % repr(err))
|
||||
|
||||
try:
|
||||
self._update_uptime(s)
|
||||
except Exception as err:
|
||||
logging.error("[agent:_fetch_stats] self.update_uptimes: %s" % repr(err))
|
||||
|
||||
try:
|
||||
self._update_advertisement(s)
|
||||
except Exception as err:
|
||||
logging.error("[agent:_fetch_stats] self.update_advertisements: %s" % repr(err))
|
||||
|
||||
try:
|
||||
self._update_peers()
|
||||
except Exception as err:
|
||||
logging.error("[agent:_fetch_stats] self.update_peers: %s" % repr(err))
|
||||
try:
|
||||
self._update_counters()
|
||||
except Exception as err:
|
||||
logging.error("[agent:_fetch_stats] self.update_counters: %s" % repr(err))
|
||||
try:
|
||||
self._update_handshakes(0)
|
||||
except Exception as err:
|
||||
logging.error("[agent:_fetch_stats] self.update_handshakes: %s" % repr(err))
|
||||
|
||||
time.sleep(5)
|
||||
|
||||
async def _on_event(self, msg):
|
||||
found_handshake = False
|
||||
jmsg = json.loads(msg)
|
||||
|
||||
# give plugins access to the events
|
||||
try:
|
||||
plugins.on('bcap_%s' % re.sub(r"[^a-z0-9_]+", "_", jmsg['tag'].lower()), self, jmsg)
|
||||
except Exception as err:
|
||||
logging.error("Processing event: %s" % err)
|
||||
|
||||
if jmsg['tag'] == 'wifi.client.handshake':
|
||||
filename = jmsg['data']['file']
|
||||
sta_mac = jmsg['data']['station']
|
||||
ap_mac = jmsg['data']['ap']
|
||||
key = "%s -> %s" % (sta_mac, ap_mac)
|
||||
if key not in self._handshakes:
|
||||
self._handshakes[key] = jmsg
|
||||
s = self.session()
|
||||
ap_and_station = self._find_ap_sta_in(sta_mac, ap_mac, s)
|
||||
if ap_and_station is None:
|
||||
logging.warning("!!! captured new handshake: %s !!!", key)
|
||||
self._last_pwnd = ap_mac
|
||||
plugins.on('handshake', self, filename, ap_mac, sta_mac)
|
||||
else:
|
||||
(ap, sta) = ap_and_station
|
||||
self._last_pwnd = ap['hostname'] if ap['hostname'] != '' and ap[
|
||||
'hostname'] != '<hidden>' else ap_mac
|
||||
logging.warning(
|
||||
"!!! captured new handshake on channel %d, %d dBm: %s (%s) -> %s [%s (%s)] !!!",
|
||||
ap['channel'], ap['rssi'], sta['mac'], sta['vendor'], ap['hostname'], ap['mac'], ap['vendor'])
|
||||
plugins.on('handshake', self, filename, ap, sta)
|
||||
found_handshake = True
|
||||
self._update_handshakes(1 if found_handshake else 0)
|
||||
|
||||
def _event_poller(self, loop):
|
||||
self._load_recovery_data()
|
||||
self.run('events.clear')
|
||||
|
||||
while True:
|
||||
logging.debug("[agent:_event_poller] polling events ...")
|
||||
try:
|
||||
loop.create_task(self.start_websocket(self._on_event))
|
||||
loop.run_forever()
|
||||
logging.debug("[agent:_event_poller] loop loop loop")
|
||||
except Exception as ex:
|
||||
logging.debug("[agent:_event_poller] Error while polling via websocket (%s)", ex)
|
||||
|
||||
def start_event_polling(self):
|
||||
# start a thread and pass in the mainloop
|
||||
_thread.start_new_thread(self._event_poller, (asyncio.get_event_loop(),))
|
||||
|
||||
def is_module_running(self, module):
|
||||
s = self.session()
|
||||
for m in s['modules']:
|
||||
if m['name'] == module:
|
||||
return m['running']
|
||||
return False
|
||||
|
||||
def start_module(self, module):
|
||||
self.run('%s on' % module)
|
||||
|
||||
def restart_module(self, module):
|
||||
self.run('%s off; %s on' % (module, module))
|
||||
|
||||
def _has_handshake(self, bssid):
|
||||
for key in self._handshakes:
|
||||
if bssid.lower() in key:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _should_interact(self, who):
|
||||
if self._has_handshake(who):
|
||||
return False
|
||||
|
||||
elif who not in self._history:
|
||||
self._history[who] = 1
|
||||
return True
|
||||
|
||||
else:
|
||||
self._history[who] += 1
|
||||
|
||||
return self._history[who] < self._config['personality']['max_interactions']
|
||||
|
||||
def associate(self, ap, throttle=-1):
|
||||
if self.is_stale():
|
||||
logging.debug("recon is stale, skipping assoc(%s)", ap['mac'])
|
||||
return
|
||||
if throttle == -1 and "throttle_a" in self._config['personality']:
|
||||
throttle = self._config['personality']['throttle_a']
|
||||
|
||||
if self._config['personality']['associate'] and self._should_interact(ap['mac']):
|
||||
self._view.on_assoc(ap)
|
||||
|
||||
try:
|
||||
logging.info("sending association frame to %s (%s %s) on channel %d [%d clients], %d dBm...",
|
||||
ap['hostname'], ap['mac'], ap['vendor'], ap['channel'], len(ap['clients']), ap['rssi'])
|
||||
self.run('wifi.assoc %s' % ap['mac'])
|
||||
self._epoch.track(assoc=True)
|
||||
except Exception as e:
|
||||
self._on_error(ap['mac'], e)
|
||||
|
||||
plugins.on('association', self, ap)
|
||||
if throttle > 0:
|
||||
time.sleep(throttle)
|
||||
self._view.on_normal()
|
||||
|
||||
def deauth(self, ap, sta, throttle=-1):
|
||||
if self.is_stale():
|
||||
logging.debug("recon is stale, skipping deauth(%s)", sta['mac'])
|
||||
return
|
||||
|
||||
if throttle == -1 and "throttle_d" in self._config['personality']:
|
||||
throttle = self._config['personality']['throttle_d']
|
||||
|
||||
if self._config['personality']['deauth'] and self._should_interact(sta['mac']):
|
||||
self._view.on_deauth(sta)
|
||||
|
||||
try:
|
||||
logging.info("deauthing %s (%s) from %s (%s %s) on channel %d, %d dBm ...",
|
||||
sta['mac'], sta['vendor'], ap['hostname'], ap['mac'], ap['vendor'], ap['channel'],
|
||||
ap['rssi'])
|
||||
self.run('wifi.deauth %s' % sta['mac'])
|
||||
self._epoch.track(deauth=True)
|
||||
except Exception as e:
|
||||
self._on_error(sta['mac'], e)
|
||||
|
||||
plugins.on('deauthentication', self, ap, sta)
|
||||
if throttle > 0:
|
||||
time.sleep(throttle)
|
||||
self._view.on_normal()
|
||||
|
||||
def set_channel(self, channel, verbose=True):
|
||||
if self.is_stale():
|
||||
logging.debug("recon is stale, skipping set_channel(%d)", channel)
|
||||
return
|
||||
|
||||
# if in the previous loop no client stations has been deauthenticated
|
||||
# and only association frames have been sent, we don't need to wait
|
||||
# very long before switching channel as we don't have to wait for
|
||||
# such client stations to reconnect in order to sniff the handshake.
|
||||
wait = 0
|
||||
if self._epoch.did_deauth:
|
||||
wait = self._config['personality']['hop_recon_time']
|
||||
elif self._epoch.did_associate:
|
||||
wait = self._config['personality']['min_recon_time']
|
||||
|
||||
if channel != self._current_channel:
|
||||
if self._current_channel != 0 and wait > 0:
|
||||
if verbose:
|
||||
logging.info("waiting for %ds on channel %d ...", wait, self._current_channel)
|
||||
else:
|
||||
logging.debug("waiting for %ds on channel %d ...", wait, self._current_channel)
|
||||
self.wait_for(wait)
|
||||
if verbose and self._epoch.any_activity:
|
||||
logging.info("CHANNEL %d", channel)
|
||||
try:
|
||||
self.run('wifi.recon.channel %d' % channel)
|
||||
self._current_channel = channel
|
||||
self._epoch.track(hop=True)
|
||||
self._view.set('channel', '%d' % channel)
|
||||
|
||||
plugins.on('channel_hop', self, channel)
|
||||
|
||||
except Exception as e:
|
||||
logging.error("Error while setting channel (%s)", e)
|
68
pwnagotchi/ai/__init__.py
Normal file
68
pwnagotchi/ai/__init__.py
Normal file
@ -0,0 +1,68 @@
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
|
||||
# https://stackoverflow.com/questions/40426502/is-there-a-way-to-suppress-the-messages-tensorflow-prints/40426709
|
||||
# os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # or any {'0', '1', '2'}
|
||||
|
||||
|
||||
def load(config, agent, epoch, from_disk=True):
|
||||
config = config['ai']
|
||||
if not config['enabled']:
|
||||
logging.info("ai disabled")
|
||||
return False
|
||||
|
||||
try:
|
||||
begin = time.time()
|
||||
|
||||
logging.info("[AI] bootstrapping dependencies ...")
|
||||
|
||||
start = time.time()
|
||||
SB_BACKEND = "stable_baselines3"
|
||||
|
||||
from stable_baselines3 import A2C
|
||||
logging.debug("[AI] A2C imported in %.2fs" % (time.time() - start))
|
||||
|
||||
start = time.time()
|
||||
from stable_baselines3.a2c import MlpPolicy
|
||||
logging.debug("[AI] MlpPolicy imported in %.2fs" % (time.time() - start))
|
||||
SB_A2C_POLICY = MlpPolicy
|
||||
|
||||
start = time.time()
|
||||
from stable_baselines3.common.vec_env import DummyVecEnv
|
||||
logging.debug("[AI] DummyVecEnv imported in %.2fs" % (time.time() - start))
|
||||
|
||||
start = time.time()
|
||||
import pwnagotchi.ai.gym as wrappers
|
||||
logging.debug("[AI] gym wrapper imported in %.2fs" % (time.time() - start))
|
||||
|
||||
env = wrappers.Environment(agent, epoch)
|
||||
env = DummyVecEnv([lambda: env])
|
||||
|
||||
logging.info("[AI] creating model ...")
|
||||
|
||||
start = time.time()
|
||||
a2c = A2C(SB_A2C_POLICY, env, **config['params'])
|
||||
logging.debug("[AI] A2C created in %.2fs" % (time.time() - start))
|
||||
|
||||
if from_disk and os.path.exists(config['path']):
|
||||
logging.info("[AI] loading %s ..." % config['path'])
|
||||
start = time.time()
|
||||
a2c.load(config['path'], env)
|
||||
logging.debug("[AI] A2C loaded in %.2fs" % (time.time() - start))
|
||||
else:
|
||||
logging.info("[AI] model created:")
|
||||
for key, value in config['params'].items():
|
||||
logging.info(" %s: %s" % (key, value))
|
||||
|
||||
logging.debug("[AI] total loading time is %.2fs" % (time.time() - begin))
|
||||
|
||||
return a2c
|
||||
except Exception as e:
|
||||
logging.info("[AI] Error while starting AI")
|
||||
logging.debug("[AI] error while starting AI (%s)", e)
|
||||
logging.info("[AI] Deleting brain and restarting.")
|
||||
os.system("rm /root/brain.nn && service pwnagotchi restart")
|
||||
|
||||
logging.warning("[AI] AI not loaded!")
|
||||
return False
|
249
pwnagotchi/ai/epoch.py
Normal file
249
pwnagotchi/ai/epoch.py
Normal file
@ -0,0 +1,249 @@
|
||||
import time
|
||||
import threading
|
||||
import logging
|
||||
|
||||
import pwnagotchi
|
||||
import pwnagotchi.utils as utils
|
||||
import pwnagotchi.mesh.wifi as wifi
|
||||
|
||||
from pwnagotchi.ai.reward import RewardFunction
|
||||
|
||||
|
||||
class Epoch(object):
|
||||
def __init__(self, config):
|
||||
self.epoch = 0
|
||||
self.config = config
|
||||
# how many consecutive epochs with no activity
|
||||
self.inactive_for = 0
|
||||
# how many consecutive epochs with activity
|
||||
self.active_for = 0
|
||||
# number of epochs with no visible access points
|
||||
self.blind_for = 0
|
||||
# number of epochs in sad state
|
||||
self.sad_for = 0
|
||||
# number of epochs in bored state
|
||||
self.bored_for = 0
|
||||
# did deauth in this epoch in the current channel?
|
||||
self.did_deauth = False
|
||||
# number of deauths in this epoch
|
||||
self.num_deauths = 0
|
||||
# did associate in this epoch in the current channel?
|
||||
self.did_associate = False
|
||||
# number of associations in this epoch
|
||||
self.num_assocs = 0
|
||||
# number of assocs or deauths missed
|
||||
self.num_missed = 0
|
||||
# did get any handshake in this epoch?
|
||||
self.did_handshakes = False
|
||||
# number of handshakes captured in this epoch
|
||||
self.num_shakes = 0
|
||||
# number of channels hops
|
||||
self.num_hops = 0
|
||||
# number of seconds sleeping
|
||||
self.num_slept = 0
|
||||
# number of peers seen during this epoch
|
||||
self.num_peers = 0
|
||||
# cumulative bond factor
|
||||
self.tot_bond_factor = 0.0 # cum_bond_factor sounded worse ...
|
||||
# average bond factor
|
||||
self.avg_bond_factor = 0.0
|
||||
# any activity at all during this epoch?
|
||||
self.any_activity = False
|
||||
# when the current epoch started
|
||||
self.epoch_started = time.time()
|
||||
# last epoch duration
|
||||
self.epoch_duration = 0
|
||||
# https://www.metageek.com/training/resources/why-channels-1-6-11.html
|
||||
self.non_overlapping_channels = {1: 0, 6: 0, 11: 0}
|
||||
# observation vectors
|
||||
self._observation = {
|
||||
'aps_histogram': [0.0] * wifi.NumChannels,
|
||||
'sta_histogram': [0.0] * wifi.NumChannels,
|
||||
'peers_histogram': [0.0] * wifi.NumChannels
|
||||
}
|
||||
self._observation_ready = threading.Event()
|
||||
self._epoch_data = {}
|
||||
self._epoch_data_ready = threading.Event()
|
||||
self._reward = RewardFunction()
|
||||
|
||||
def wait_for_epoch_data(self, with_observation=True, timeout=None):
|
||||
# if with_observation:
|
||||
# self._observation_ready.wait(timeout)
|
||||
# self._observation_ready.clear()
|
||||
self._epoch_data_ready.wait(timeout)
|
||||
self._epoch_data_ready.clear()
|
||||
return self._epoch_data if with_observation is False else {**self._observation, **self._epoch_data}
|
||||
|
||||
def data(self):
|
||||
return self._epoch_data
|
||||
|
||||
def observe(self, aps, peers):
|
||||
num_aps = len(aps)
|
||||
if num_aps == 0:
|
||||
self.blind_for += 1
|
||||
else:
|
||||
self.blind_for = 0
|
||||
|
||||
bond_unit_scale = self.config['personality']['bond_encounters_factor']
|
||||
|
||||
self.num_peers = len(peers)
|
||||
num_peers = self.num_peers + 1e-10 # avoid division by 0
|
||||
|
||||
self.tot_bond_factor = sum((peer.encounters for peer in peers)) / bond_unit_scale
|
||||
self.avg_bond_factor = self.tot_bond_factor / num_peers
|
||||
|
||||
num_aps = len(aps) + 1e-10
|
||||
num_sta = sum(len(ap['clients']) for ap in aps) + 1e-10
|
||||
aps_per_chan = [0.0] * wifi.NumChannels
|
||||
sta_per_chan = [0.0] * wifi.NumChannels
|
||||
peers_per_chan = [0.0] * wifi.NumChannels
|
||||
|
||||
for ap in aps:
|
||||
ch_idx = ap['channel'] - 1
|
||||
try:
|
||||
aps_per_chan[ch_idx] += 1.0
|
||||
sta_per_chan[ch_idx] += len(ap['clients'])
|
||||
except IndexError:
|
||||
logging.error("got data on channel %d, we can store %d channels" % (ap['channel'], wifi.NumChannels))
|
||||
|
||||
for peer in peers:
|
||||
try:
|
||||
peers_per_chan[peer.last_channel - 1] += 1.0
|
||||
except IndexError:
|
||||
logging.error(
|
||||
"got peer data on channel %d, we can store %d channels" % (peer.last_channel, wifi.NumChannels))
|
||||
|
||||
# normalize
|
||||
aps_per_chan = [e / num_aps for e in aps_per_chan]
|
||||
sta_per_chan = [e / num_sta for e in sta_per_chan]
|
||||
peers_per_chan = [e / num_peers for e in peers_per_chan]
|
||||
|
||||
self._observation = {
|
||||
'aps_histogram': aps_per_chan,
|
||||
'sta_histogram': sta_per_chan,
|
||||
'peers_histogram': peers_per_chan
|
||||
}
|
||||
self._observation_ready.set()
|
||||
|
||||
def track(self, deauth=False, assoc=False, handshake=False, hop=False, sleep=False, miss=False, inc=1):
|
||||
if deauth:
|
||||
self.num_deauths += inc
|
||||
self.did_deauth = True
|
||||
self.any_activity = True
|
||||
|
||||
if assoc:
|
||||
self.num_assocs += inc
|
||||
self.did_associate = True
|
||||
self.any_activity = True
|
||||
|
||||
if miss:
|
||||
self.num_missed += inc
|
||||
|
||||
if hop:
|
||||
self.num_hops += inc
|
||||
# these two are used in order to determine the sleep time in seconds
|
||||
# before switching to a new channel ... if nothing happened so far
|
||||
# during this epoch on the current channel, we will sleep less
|
||||
self.did_deauth = False
|
||||
self.did_associate = False
|
||||
|
||||
if handshake:
|
||||
self.num_shakes += inc
|
||||
self.did_handshakes = True
|
||||
|
||||
if sleep:
|
||||
self.num_slept += inc
|
||||
|
||||
def next(self):
|
||||
if self.any_activity is False and self.did_handshakes is False:
|
||||
self.inactive_for += 1
|
||||
self.active_for = 0
|
||||
else:
|
||||
self.active_for += 1
|
||||
self.inactive_for = 0
|
||||
self.sad_for = 0
|
||||
self.bored_for = 0
|
||||
|
||||
if self.inactive_for >= self.config['personality']['sad_num_epochs']:
|
||||
# sad > bored; cant be sad and bored
|
||||
self.bored_for = 0
|
||||
self.sad_for += 1
|
||||
elif self.inactive_for >= self.config['personality']['bored_num_epochs']:
|
||||
# sad_treshhold > inactive > bored_treshhold; cant be sad and bored
|
||||
self.sad_for = 0
|
||||
self.bored_for += 1
|
||||
else:
|
||||
self.sad_for = 0
|
||||
self.bored_for = 0
|
||||
|
||||
now = time.time()
|
||||
cpu = pwnagotchi.cpu_load("epoch")
|
||||
mem = pwnagotchi.mem_usage()
|
||||
temp = pwnagotchi.temperature()
|
||||
|
||||
self.epoch_duration = now - self.epoch_started
|
||||
|
||||
# cache the state of this epoch for other threads to read
|
||||
self._epoch_data = {
|
||||
'duration_secs': self.epoch_duration,
|
||||
'slept_for_secs': self.num_slept,
|
||||
'blind_for_epochs': self.blind_for,
|
||||
'inactive_for_epochs': self.inactive_for,
|
||||
'active_for_epochs': self.active_for,
|
||||
'sad_for_epochs': self.sad_for,
|
||||
'bored_for_epochs': self.bored_for,
|
||||
'missed_interactions': self.num_missed,
|
||||
'num_hops': self.num_hops,
|
||||
'num_peers': self.num_peers,
|
||||
'tot_bond': self.tot_bond_factor,
|
||||
'avg_bond': self.avg_bond_factor,
|
||||
'num_deauths': self.num_deauths,
|
||||
'num_associations': self.num_assocs,
|
||||
'num_handshakes': self.num_shakes,
|
||||
'cpu_load': cpu,
|
||||
'mem_usage': mem,
|
||||
'temperature': temp
|
||||
}
|
||||
|
||||
self._epoch_data['reward'] = self._reward(self.epoch + 1, self._epoch_data)
|
||||
self._epoch_data_ready.set()
|
||||
|
||||
logging.info("[epoch %d] duration=%s slept_for=%s blind=%d sad=%d bored=%d inactive=%d active=%d peers=%d tot_bond=%.2f "
|
||||
"avg_bond=%.2f hops=%d missed=%d deauths=%d assocs=%d handshakes=%d cpu=%d%% mem=%d%% "
|
||||
"temperature=%dC reward=%s" % (
|
||||
self.epoch,
|
||||
utils.secs_to_hhmmss(self.epoch_duration),
|
||||
utils.secs_to_hhmmss(self.num_slept),
|
||||
self.blind_for,
|
||||
self.sad_for,
|
||||
self.bored_for,
|
||||
self.inactive_for,
|
||||
self.active_for,
|
||||
self.num_peers,
|
||||
self.tot_bond_factor,
|
||||
self.avg_bond_factor,
|
||||
self.num_hops,
|
||||
self.num_missed,
|
||||
self.num_deauths,
|
||||
self.num_assocs,
|
||||
self.num_shakes,
|
||||
cpu * 100,
|
||||
mem * 100,
|
||||
temp,
|
||||
self._epoch_data['reward']))
|
||||
|
||||
self.epoch += 1
|
||||
self.epoch_started = now
|
||||
self.did_deauth = False
|
||||
self.num_deauths = 0
|
||||
self.num_peers = 0
|
||||
self.tot_bond_factor = 0.0
|
||||
self.avg_bond_factor = 0.0
|
||||
self.did_associate = False
|
||||
self.num_assocs = 0
|
||||
self.num_missed = 0
|
||||
self.did_handshakes = False
|
||||
self.num_shakes = 0
|
||||
self.num_hops = 0
|
||||
self.num_slept = 0
|
||||
self.any_activity = False
|
61
pwnagotchi/ai/featurizer.py
Normal file
61
pwnagotchi/ai/featurizer.py
Normal file
@ -0,0 +1,61 @@
|
||||
import numpy as np
|
||||
|
||||
import pwnagotchi.mesh.wifi as wifi
|
||||
|
||||
MAX_EPOCH_DURATION = 1024
|
||||
|
||||
|
||||
histogram_size = wifi.NumChannels
|
||||
|
||||
shape = (1,
|
||||
# aps per channel
|
||||
histogram_size +
|
||||
# clients per channel
|
||||
histogram_size +
|
||||
# peers per channel
|
||||
histogram_size +
|
||||
# duration
|
||||
1 +
|
||||
# inactive
|
||||
1 +
|
||||
# active
|
||||
1 +
|
||||
# missed
|
||||
1 +
|
||||
# hops
|
||||
1 +
|
||||
# deauths
|
||||
1 +
|
||||
# assocs
|
||||
1 +
|
||||
# handshakes
|
||||
1)
|
||||
|
||||
|
||||
def featurize(state, step):
|
||||
tot_epochs = step + 1e-10
|
||||
tot_interactions = (state['num_deauths'] + state['num_associations']) + 1e-10
|
||||
return np.concatenate((
|
||||
# aps per channel
|
||||
state['aps_histogram'],
|
||||
# clients per channel
|
||||
state['sta_histogram'],
|
||||
# peers per channel
|
||||
state['peers_histogram'],
|
||||
# duration
|
||||
[np.clip(state['duration_secs'] / MAX_EPOCH_DURATION, 0.0, 1.0)],
|
||||
# inactive
|
||||
[state['inactive_for_epochs'] / tot_epochs],
|
||||
# active
|
||||
[state['active_for_epochs'] / tot_epochs],
|
||||
# missed
|
||||
[state['missed_interactions'] / tot_interactions],
|
||||
# hops
|
||||
[state['num_hops'] / wifi.NumChannels],
|
||||
# deauths
|
||||
[state['num_deauths'] / tot_interactions],
|
||||
# assocs
|
||||
[state['num_associations'] / tot_interactions],
|
||||
# handshakes
|
||||
[state['num_handshakes'] / tot_interactions],
|
||||
))
|
148
pwnagotchi/ai/gym.py
Normal file
148
pwnagotchi/ai/gym.py
Normal file
@ -0,0 +1,148 @@
|
||||
import logging
|
||||
import gym
|
||||
from gym import spaces
|
||||
import numpy as np
|
||||
|
||||
import pwnagotchi.ai.featurizer as featurizer
|
||||
import pwnagotchi.ai.reward as reward
|
||||
from pwnagotchi.ai.parameter import Parameter
|
||||
|
||||
|
||||
class Environment(gym.Env):
|
||||
render_mode = "human"
|
||||
metadata = {'render_modes': ['human']}
|
||||
params = [
|
||||
Parameter('min_rssi', min_value=-200, max_value=-50),
|
||||
Parameter('ap_ttl', min_value=30, max_value=600),
|
||||
Parameter('sta_ttl', min_value=60, max_value=300),
|
||||
|
||||
Parameter('recon_time', min_value=5, max_value=60),
|
||||
Parameter('max_inactive_scale', min_value=3, max_value=10),
|
||||
Parameter('recon_inactive_multiplier', min_value=1, max_value=3),
|
||||
Parameter('hop_recon_time', min_value=5, max_value=60),
|
||||
Parameter('min_recon_time', min_value=1, max_value=30),
|
||||
Parameter('max_interactions', min_value=1, max_value=25),
|
||||
Parameter('max_misses_for_recon', min_value=3, max_value=10),
|
||||
Parameter('excited_num_epochs', min_value=5, max_value=30),
|
||||
Parameter('bored_num_epochs', min_value=5, max_value=30),
|
||||
Parameter('sad_num_epochs', min_value=5, max_value=30),
|
||||
]
|
||||
|
||||
def __init__(self, agent, epoch):
|
||||
super(Environment, self).__init__()
|
||||
self._agent = agent
|
||||
self._epoch = epoch
|
||||
self._epoch_num = 0
|
||||
self._last_render = None
|
||||
|
||||
channels = agent.supported_channels()
|
||||
|
||||
Environment.params += [
|
||||
Parameter('_channel_%d' % ch, min_value=0, max_value=1, meta=ch + 1) for ch in
|
||||
range(featurizer.histogram_size) if ch + 1 in channels
|
||||
]
|
||||
|
||||
self.last = {
|
||||
'reward': 0.0,
|
||||
'observation': None,
|
||||
'policy': None,
|
||||
'params': {},
|
||||
'state': None,
|
||||
'state_v': None
|
||||
}
|
||||
|
||||
self.action_space = spaces.MultiDiscrete([p.space_size() for p in Environment.params if p.trainable])
|
||||
self.observation_space = spaces.Box(low=0, high=1, shape=featurizer.shape, dtype=np.float32)
|
||||
self.reward_range = reward.range
|
||||
|
||||
@staticmethod
|
||||
def policy_size():
|
||||
return len(list(p for p in Environment.params if p.trainable))
|
||||
|
||||
@staticmethod
|
||||
def policy_to_params(policy):
|
||||
num = len(policy)
|
||||
params = {}
|
||||
|
||||
assert len(Environment.params) == num
|
||||
|
||||
channels = []
|
||||
|
||||
for i in range(num):
|
||||
param = Environment.params[i]
|
||||
|
||||
if '_channel' not in param.name:
|
||||
params[param.name] = param.to_param_value(policy[i])
|
||||
else:
|
||||
has_chan = param.to_param_value(policy[i])
|
||||
# print("%s policy:%s bool:%s" % (param.name, policy[i], has_chan))
|
||||
chan = param.meta
|
||||
if has_chan:
|
||||
channels.append(chan)
|
||||
|
||||
params['channels'] = channels
|
||||
|
||||
return params
|
||||
|
||||
def _next_epoch(self):
|
||||
logging.debug("[ai] waiting for epoch to finish ...")
|
||||
return self._epoch.wait_for_epoch_data()
|
||||
|
||||
def _apply_policy(self, policy):
|
||||
new_params = Environment.policy_to_params(policy)
|
||||
self.last['policy'] = policy
|
||||
self.last['params'] = new_params
|
||||
self._agent.on_ai_policy(new_params)
|
||||
|
||||
def step(self, policy):
|
||||
# create the parameters from the policy and update
|
||||
# them in the algorithm
|
||||
self._apply_policy(policy)
|
||||
self._epoch_num += 1
|
||||
|
||||
# wait for the algorithm to run with the new parameters
|
||||
state = self._next_epoch()
|
||||
|
||||
self.last['reward'] = state['reward']
|
||||
self.last['state'] = state
|
||||
self.last['state_v'] = featurizer.featurize(state, self._epoch_num)
|
||||
|
||||
self._agent.on_ai_step()
|
||||
|
||||
return self.last['state_v'], self.last['reward'], not self._agent.is_training(), {}
|
||||
|
||||
def reset(self):
|
||||
# logging.info("[ai] resetting environment ...")
|
||||
self._epoch_num = 0
|
||||
state = self._next_epoch()
|
||||
self.last['state'] = state
|
||||
self.last['state_v'] = featurizer.featurize(state, 1)
|
||||
return self.last['state_v']
|
||||
|
||||
def _render_histogram(self, hist):
|
||||
for ch in range(featurizer.histogram_size):
|
||||
if hist[ch]:
|
||||
logging.info(" CH %d: %s" % (ch + 1, hist[ch]))
|
||||
|
||||
def render(self, mode='human', close=False, force=False):
|
||||
# when using a vectorialized environment, render gets called twice
|
||||
# avoid rendering the same data
|
||||
if self._last_render == self._epoch_num:
|
||||
return
|
||||
|
||||
if not self._agent.is_training() and not force:
|
||||
return
|
||||
|
||||
self._last_render = self._epoch_num
|
||||
|
||||
logging.info("[AI] --- training epoch %d/%d ---" % (self._epoch_num, self._agent.training_epochs()))
|
||||
logging.info("[AI] REWARD: %f" % self.last['reward'])
|
||||
|
||||
logging.debug(
|
||||
"[AI] policy: %s" % ', '.join("%s:%s" % (name, value) for name, value in self.last['params'].items()))
|
||||
|
||||
logging.info("[AI] observation:")
|
||||
for name, value in self.last['state'].items():
|
||||
if 'histogram' in name:
|
||||
logging.info(" %s" % name.replace('_histogram', ''))
|
||||
self._render_histogram(value)
|
30
pwnagotchi/ai/parameter.py
Normal file
30
pwnagotchi/ai/parameter.py
Normal file
@ -0,0 +1,30 @@
|
||||
from gym import spaces
|
||||
|
||||
|
||||
class Parameter(object):
|
||||
def __init__(self, name, value=0.0, min_value=0, max_value=2, meta=None, trainable=True):
|
||||
self.name = name
|
||||
self.trainable = trainable
|
||||
self.meta = meta
|
||||
self.value = value
|
||||
self.min_value = min_value
|
||||
self.max_value = max_value + 1
|
||||
|
||||
# gymnasium.space.Discrete is within [0, 1, 2, ..., n-1]
|
||||
if self.min_value < 0:
|
||||
self.scale_factor = abs(self.min_value)
|
||||
elif self.min_value > 0:
|
||||
self.scale_factor = -self.min_value
|
||||
else:
|
||||
self.scale_factor = 0
|
||||
|
||||
def space_size(self):
|
||||
return self.max_value + self.scale_factor
|
||||
|
||||
def space(self):
|
||||
return spaces.Discrete(self.max_value + self.scale_factor)
|
||||
|
||||
def to_param_value(self, policy_v):
|
||||
self.value = policy_v - self.scale_factor
|
||||
assert self.min_value <= self.value <= self.max_value
|
||||
return int(self.value)
|
27
pwnagotchi/ai/reward.py
Normal file
27
pwnagotchi/ai/reward.py
Normal file
@ -0,0 +1,27 @@
|
||||
import pwnagotchi.mesh.wifi as wifi
|
||||
|
||||
range = (-.7, 1.02)
|
||||
fuck_zero = 1e-20
|
||||
|
||||
|
||||
class RewardFunction(object):
|
||||
def __call__(self, epoch_n, state):
|
||||
tot_epochs = epoch_n + fuck_zero
|
||||
tot_interactions = max(state['num_deauths'] + state['num_associations'], state['num_handshakes']) + fuck_zero
|
||||
tot_channels = wifi.NumChannels
|
||||
|
||||
h = state['num_handshakes'] / tot_interactions
|
||||
a = .2 * (state['active_for_epochs'] / tot_epochs)
|
||||
c = .1 * (state['num_hops'] / tot_channels)
|
||||
|
||||
b = -.3 * (state['blind_for_epochs'] / tot_epochs)
|
||||
m = -.3 * (state['missed_interactions'] / tot_interactions)
|
||||
i = -.2 * (state['inactive_for_epochs'] / tot_epochs)
|
||||
|
||||
# include emotions if state >= 5 epochs
|
||||
_sad = state['sad_for_epochs'] if state['sad_for_epochs'] >= 5 else 0
|
||||
_bored = state['bored_for_epochs'] if state['bored_for_epochs'] >= 5 else 0
|
||||
s = -.2 * (_sad / tot_epochs)
|
||||
l = -.1 * (_bored / tot_epochs)
|
||||
|
||||
return h + a + c + b + i + m + s + l
|
197
pwnagotchi/ai/train.py
Normal file
197
pwnagotchi/ai/train.py
Normal file
@ -0,0 +1,197 @@
|
||||
import _thread
|
||||
import threading
|
||||
import time
|
||||
import random
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
|
||||
import pwnagotchi.plugins as plugins
|
||||
import pwnagotchi.ai as ai
|
||||
|
||||
|
||||
class Stats(object):
|
||||
def __init__(self, path, events_receiver):
|
||||
self._lock = threading.Lock()
|
||||
self._receiver = events_receiver
|
||||
|
||||
self.path = path
|
||||
self.born_at = time.time()
|
||||
# total epochs lived (trained + just eval)
|
||||
self.epochs_lived = 0
|
||||
# total training epochs
|
||||
self.epochs_trained = 0
|
||||
|
||||
self.worst_reward = 0.0
|
||||
self.best_reward = 0.0
|
||||
|
||||
self.load()
|
||||
|
||||
def on_epoch(self, data, training):
|
||||
best_r = False
|
||||
worst_r = False
|
||||
with self._lock:
|
||||
reward = data['reward']
|
||||
if reward < self.worst_reward:
|
||||
self.worst_reward = reward
|
||||
worst_r = True
|
||||
|
||||
elif reward > self.best_reward:
|
||||
best_r = True
|
||||
self.best_reward = reward
|
||||
|
||||
self.epochs_lived += 1
|
||||
if training:
|
||||
self.epochs_trained += 1
|
||||
|
||||
self.save()
|
||||
|
||||
if best_r:
|
||||
self._receiver.on_ai_best_reward(reward)
|
||||
elif worst_r:
|
||||
self._receiver.on_ai_worst_reward(reward)
|
||||
|
||||
def load(self):
|
||||
with self._lock:
|
||||
if os.path.exists(self.path) and os.path.getsize(self.path) > 0:
|
||||
logging.info("[AI] loading %s" % self.path)
|
||||
with open(self.path, 'rt') as fp:
|
||||
obj = json.load(fp)
|
||||
|
||||
self.born_at = obj['born_at']
|
||||
self.epochs_lived, self.epochs_trained = obj['epochs_lived'], obj['epochs_trained']
|
||||
self.best_reward, self.worst_reward = obj['rewards']['best'], obj['rewards']['worst']
|
||||
|
||||
def save(self):
|
||||
with self._lock:
|
||||
logging.info("[AI] saving %s" % self.path)
|
||||
|
||||
data = json.dumps({
|
||||
'born_at': self.born_at,
|
||||
'epochs_lived': self.epochs_lived,
|
||||
'epochs_trained': self.epochs_trained,
|
||||
'rewards': {
|
||||
'best': self.best_reward,
|
||||
'worst': self.worst_reward
|
||||
}
|
||||
})
|
||||
|
||||
temp = "%s.tmp" % self.path
|
||||
back = "%s.bak" % self.path
|
||||
with open(temp, 'wt') as fp:
|
||||
fp.write(data)
|
||||
|
||||
if os.path.isfile(self.path):
|
||||
os.replace(self.path, back)
|
||||
os.replace(temp, self.path)
|
||||
|
||||
|
||||
class AsyncTrainer(object):
|
||||
def __init__(self, config):
|
||||
self._config = config
|
||||
self._model = None
|
||||
self._is_training = False
|
||||
self._training_epochs = 0
|
||||
self._nn_path = self._config['ai']['path']
|
||||
self._stats = Stats("%s.json" % os.path.splitext(self._nn_path)[0], self)
|
||||
|
||||
def set_training(self, training, for_epochs=0):
|
||||
self._is_training = training
|
||||
self._training_epochs = for_epochs
|
||||
|
||||
if training:
|
||||
plugins.on('ai_training_start', self, for_epochs)
|
||||
else:
|
||||
plugins.on('ai_training_end', self)
|
||||
|
||||
def is_training(self):
|
||||
return self._is_training
|
||||
|
||||
def training_epochs(self):
|
||||
return self._training_epochs
|
||||
|
||||
def start_ai(self):
|
||||
_thread.start_new_thread(self._ai_worker, ())
|
||||
|
||||
def _save_ai(self):
|
||||
logging.info("[AI] saving model to %s ..." % self._nn_path)
|
||||
temp = "%s.tmp" % self._nn_path
|
||||
self._model.save(temp)
|
||||
os.replace(temp, self._nn_path)
|
||||
|
||||
def on_ai_step(self):
|
||||
self._model.env.render()
|
||||
|
||||
if self._is_training:
|
||||
self._save_ai()
|
||||
|
||||
self._stats.on_epoch(self._epoch.data(), self._is_training)
|
||||
|
||||
def on_ai_training_step(self, _locals, _globals):
|
||||
self._model.env.render()
|
||||
plugins.on('ai_training_step', self, _locals, _globals)
|
||||
|
||||
def on_ai_policy(self, new_params):
|
||||
plugins.on('ai_policy', self, new_params)
|
||||
logging.info("[AI] setting new policy:")
|
||||
for name, value in new_params.items():
|
||||
if name in self._config['personality']:
|
||||
curr_value = self._config['personality'][name]
|
||||
if curr_value != value:
|
||||
logging.info("[AI] ! %s: %s -> %s" % (name, curr_value, value))
|
||||
self._config['personality'][name] = value
|
||||
else:
|
||||
logging.error("[AI] param %s not in personality configuration!" % name)
|
||||
|
||||
self.run('set wifi.ap.ttl %d' % self._config['personality']['ap_ttl'])
|
||||
self.run('set wifi.sta.ttl %d' % self._config['personality']['sta_ttl'])
|
||||
self.run('set wifi.rssi.min %d' % self._config['personality']['min_rssi'])
|
||||
|
||||
def on_ai_ready(self):
|
||||
self._view.on_ai_ready()
|
||||
plugins.on('ai_ready', self)
|
||||
|
||||
def on_ai_best_reward(self, r):
|
||||
logging.info("[AI] best reward so far: %s" % r)
|
||||
self._view.on_motivated(r)
|
||||
plugins.on('ai_best_reward', self, r)
|
||||
|
||||
def on_ai_worst_reward(self, r):
|
||||
logging.info("[AI] worst reward so far: %s" % r)
|
||||
self._view.on_demotivated(r)
|
||||
plugins.on('ai_worst_reward', self, r)
|
||||
|
||||
def _ai_worker(self):
|
||||
self._model = ai.load(self._config, self, self._epoch)
|
||||
|
||||
if self._model:
|
||||
self.on_ai_ready()
|
||||
|
||||
epochs_per_episode = self._config['ai']['epochs_per_episode']
|
||||
|
||||
obs = None
|
||||
while True:
|
||||
self._model.env.render()
|
||||
# enter in training mode?
|
||||
if random.random() > self._config['ai']['laziness']:
|
||||
logging.info("[AI] learning for %d epochs ..." % epochs_per_episode)
|
||||
try:
|
||||
self.set_training(True, epochs_per_episode)
|
||||
# back up brain file before starting new training set
|
||||
if os.path.isfile(self._nn_path):
|
||||
back = "%s.bak" % self._nn_path
|
||||
os.replace(self._nn_path, back)
|
||||
self._view.set("mode", " AI")
|
||||
self._model.learn(total_timesteps=epochs_per_episode, callback=self.on_ai_training_step)
|
||||
except Exception as e:
|
||||
logging.exception("[AI] error while training (%s)", e)
|
||||
finally:
|
||||
self.set_training(False)
|
||||
obs = self._model.env.reset()
|
||||
# init the first time
|
||||
elif obs is None:
|
||||
obs = self._model.env.reset()
|
||||
|
||||
# run the inference
|
||||
action, _ = self._model.predict(obs)
|
||||
obs, _, _, _ = self._model.env.step(action)
|
16
pwnagotchi/ai/utils.py
Normal file
16
pwnagotchi/ai/utils.py
Normal file
@ -0,0 +1,16 @@
|
||||
import numpy as np
|
||||
|
||||
|
||||
def normalize(v, min_v, max_v):
|
||||
return (v - min_v) / (max_v - min_v)
|
||||
|
||||
|
||||
def as_batches(x, y, batch_size, shuffle=True):
|
||||
x_size = len(x)
|
||||
assert x_size == len(y)
|
||||
|
||||
indices = np.random.permutation(x_size) if shuffle else None
|
||||
|
||||
for offset in range(0, x_size - batch_size + 1, batch_size):
|
||||
excerpt = indices[offset:offset + batch_size] if shuffle else slice(offset, offset + batch_size)
|
||||
yield x[excerpt], y[excerpt]
|
143
pwnagotchi/automata.py
Normal file
143
pwnagotchi/automata.py
Normal file
@ -0,0 +1,143 @@
|
||||
import logging
|
||||
|
||||
import pwnagotchi.plugins as plugins
|
||||
from pwnagotchi.ai.epoch import Epoch
|
||||
|
||||
|
||||
# basic mood system
|
||||
class Automata(object):
|
||||
def __init__(self, config, view):
|
||||
self._config = config
|
||||
self._view = view
|
||||
self._epoch = Epoch(config)
|
||||
|
||||
def _on_miss(self, who):
|
||||
logging.info("it looks like %s is not in range anymore :/", who)
|
||||
self._epoch.track(miss=True)
|
||||
self._view.on_miss(who)
|
||||
|
||||
def _on_error(self, who, e):
|
||||
# when we're trying to associate or deauth something that is not in range anymore
|
||||
# (if we are moving), we get the following error from bettercap:
|
||||
# error 400: 50:c7:bf:2e:d3:37 is an unknown BSSID or it is in the association skip list.
|
||||
if 'is an unknown BSSID' in str(e):
|
||||
self._on_miss(who)
|
||||
else:
|
||||
logging.error(e)
|
||||
|
||||
def set_starting(self):
|
||||
self._view.on_starting()
|
||||
|
||||
def set_ready(self):
|
||||
plugins.on('ready', self)
|
||||
|
||||
def in_good_mood(self):
|
||||
return self._has_support_network_for(1.0)
|
||||
|
||||
def _has_support_network_for(self, factor):
|
||||
bond_factor = self._config['personality']['bond_encounters_factor']
|
||||
total_encounters = sum(peer.encounters for _, peer in self._peers.items())
|
||||
support_factor = total_encounters / bond_factor
|
||||
return support_factor >= factor
|
||||
|
||||
# triggered when it's a sad/bad day, but you have good friends around ^_^
|
||||
def set_grateful(self):
|
||||
self._view.on_grateful()
|
||||
plugins.on('grateful', self)
|
||||
|
||||
def set_lonely(self):
|
||||
if not self._has_support_network_for(1.0):
|
||||
logging.info("unit is lonely")
|
||||
self._view.on_lonely()
|
||||
plugins.on('lonely', self)
|
||||
else:
|
||||
logging.info("unit is grateful instead of lonely")
|
||||
self.set_grateful()
|
||||
|
||||
def set_bored(self):
|
||||
factor = self._epoch.inactive_for / self._config['personality']['bored_num_epochs']
|
||||
if not self._has_support_network_for(factor):
|
||||
logging.warning("%d epochs with no activity -> bored", self._epoch.inactive_for)
|
||||
self._view.on_bored()
|
||||
plugins.on('bored', self)
|
||||
else:
|
||||
logging.info("unit is grateful instead of bored")
|
||||
self.set_grateful()
|
||||
|
||||
def set_sad(self):
|
||||
factor = self._epoch.inactive_for / self._config['personality']['sad_num_epochs']
|
||||
if not self._has_support_network_for(factor):
|
||||
logging.warning("%d epochs with no activity -> sad", self._epoch.inactive_for)
|
||||
self._view.on_sad()
|
||||
plugins.on('sad', self)
|
||||
else:
|
||||
logging.info("unit is grateful instead of sad")
|
||||
self.set_grateful()
|
||||
|
||||
def set_angry(self, factor):
|
||||
if not self._has_support_network_for(factor):
|
||||
logging.warning("%d epochs with no activity -> angry", self._epoch.inactive_for)
|
||||
self._view.on_angry()
|
||||
plugins.on('angry', self)
|
||||
else:
|
||||
logging.info("unit is grateful instead of angry")
|
||||
self.set_grateful()
|
||||
|
||||
def set_excited(self):
|
||||
logging.warning("%d epochs with activity -> excited", self._epoch.active_for)
|
||||
self._view.on_excited()
|
||||
plugins.on('excited', self)
|
||||
|
||||
def set_rebooting(self):
|
||||
self._view.on_rebooting()
|
||||
plugins.on('rebooting', self)
|
||||
|
||||
def wait_for(self, t, sleeping=True):
|
||||
plugins.on('sleep' if sleeping else 'wait', self, t)
|
||||
self._view.wait(t, sleeping)
|
||||
self._epoch.track(sleep=True, inc=t)
|
||||
|
||||
def is_stale(self):
|
||||
return self._epoch.num_missed > self._config['personality']['max_misses_for_recon']
|
||||
|
||||
def any_activity(self):
|
||||
return self._epoch.any_activity
|
||||
|
||||
def next_epoch(self):
|
||||
logging.debug("agent.next_epoch()")
|
||||
|
||||
was_stale = self.is_stale()
|
||||
did_miss = self._epoch.num_missed
|
||||
|
||||
self._epoch.next()
|
||||
|
||||
# after X misses during an epoch, set the status to lonely or angry
|
||||
if was_stale:
|
||||
factor = did_miss / self._config['personality']['max_misses_for_recon']
|
||||
if factor >= 2.0:
|
||||
self.set_angry(factor)
|
||||
else:
|
||||
logging.warning("agent missed %d interactions -> lonely", did_miss)
|
||||
self.set_lonely()
|
||||
# after X times being bored, the status is set to sad or angry
|
||||
elif self._epoch.sad_for:
|
||||
factor = self._epoch.inactive_for / self._config['personality']['sad_num_epochs']
|
||||
if factor >= 2.0:
|
||||
self.set_angry(factor)
|
||||
else:
|
||||
self.set_sad()
|
||||
# after X times being inactive, the status is set to bored
|
||||
elif self._epoch.bored_for:
|
||||
self.set_bored()
|
||||
# after X times being active, the status is set to happy / excited
|
||||
elif self._epoch.active_for >= self._config['personality']['excited_num_epochs']:
|
||||
self.set_excited()
|
||||
elif self._epoch.active_for >= 5 and self._has_support_network_for(5.0):
|
||||
self.set_grateful()
|
||||
|
||||
plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data())
|
||||
|
||||
if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']:
|
||||
logging.critical("%d epochs without visible access points -> restarting ...", self._epoch.blind_for)
|
||||
self._restart()
|
||||
self._epoch.blind_for = 0
|
119
pwnagotchi/bettercap.py
Normal file
119
pwnagotchi/bettercap.py
Normal file
@ -0,0 +1,119 @@
|
||||
import logging
|
||||
import requests
|
||||
import websockets
|
||||
import asyncio
|
||||
import random
|
||||
|
||||
from requests.auth import HTTPBasicAuth
|
||||
from time import sleep
|
||||
|
||||
import pwnagotchi
|
||||
|
||||
requests.adapters.DEFAULT_RETRIES = 5 # increase retries number
|
||||
|
||||
ping_timeout = 180
|
||||
ping_interval = 15
|
||||
max_queue = 10000
|
||||
|
||||
min_sleep = 0.5
|
||||
max_sleep = 5.0
|
||||
|
||||
|
||||
def decode(r, verbose_errors=True):
|
||||
try:
|
||||
return r.json()
|
||||
except Exception as e:
|
||||
if r.status_code == 200:
|
||||
logging.error("error while decoding json: error='%s' resp='%s'" % (e, r.text))
|
||||
else:
|
||||
err = "error %d: %s" % (r.status_code, r.text.strip())
|
||||
if verbose_errors:
|
||||
logging.info(err)
|
||||
raise Exception(err)
|
||||
return r.text
|
||||
|
||||
|
||||
class Client(object):
|
||||
def __init__(self, hostname='localhost', scheme='http', port=8081, username='user', password='pass'):
|
||||
self.hostname = hostname
|
||||
self.scheme = scheme
|
||||
self.port = port
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.url = "%s://%s:%d/api" % (scheme, hostname, port)
|
||||
self.websocket = "ws://%s:%s@%s:%d/api" % (username, password, hostname, port)
|
||||
self.auth = HTTPBasicAuth(username, password)
|
||||
|
||||
# session takes optional argument to pull a sub-dictionary
|
||||
# ex.: "session/wifi", "session/ble"
|
||||
def session(self, sess="session"):
|
||||
r = requests.get("%s/%s" % (self.url, sess), auth=self.auth)
|
||||
return decode(r)
|
||||
|
||||
async def start_websocket(self, consumer):
|
||||
s = "%s/events" % self.websocket
|
||||
|
||||
# More modern version of the approach below
|
||||
# logging.info("Creating new websocket...")
|
||||
# async for ws in websockets.connect(s):
|
||||
# try:
|
||||
# async for msg in ws:
|
||||
# try:
|
||||
# await consumer(msg)
|
||||
# except Exception as ex:
|
||||
# logging.debug("Error while parsing event (%s)", ex)
|
||||
# except websockets.exceptions.ConnectionClosedError:
|
||||
# sleep_time = max_sleep*random.random()
|
||||
# logging.warning('Retrying websocket connection in {} sec'.format(sleep_time))
|
||||
# await asyncio.sleep(sleep_time)
|
||||
# continue
|
||||
|
||||
# restarted every time the connection fails
|
||||
while True:
|
||||
logging.info("[bettercap] creating new websocket...")
|
||||
try:
|
||||
async with websockets.connect(s, ping_interval=ping_interval, ping_timeout=ping_timeout,
|
||||
max_queue=max_queue) as ws:
|
||||
# listener loop
|
||||
while True:
|
||||
try:
|
||||
async for msg in ws:
|
||||
try:
|
||||
await consumer(msg)
|
||||
except Exception as ex:
|
||||
logging.debug("[bettercap] error while parsing event (%s)", ex)
|
||||
except websockets.ConnectionClosedError:
|
||||
try:
|
||||
pong = await ws.ping()
|
||||
await asyncio.wait_for(pong, timeout=ping_timeout)
|
||||
logging.warning('[bettercap] ping OK, keeping connection alive...')
|
||||
continue
|
||||
except:
|
||||
sleep_time = min_sleep + max_sleep*random.random()
|
||||
logging.warning('[bettercap] ping error - retrying connection in {} sec'.format(sleep_time))
|
||||
await asyncio.sleep(sleep_time)
|
||||
break
|
||||
except ConnectionRefusedError:
|
||||
sleep_time = min_sleep + max_sleep*random.random()
|
||||
logging.warning('[bettercap] nobody seems to be listening at the bettercap endpoint...')
|
||||
logging.warning('[bettercap] retrying connection in {} sec'.format(sleep_time))
|
||||
await asyncio.sleep(sleep_time)
|
||||
continue
|
||||
except OSError:
|
||||
sleep_time = min_sleep + max_sleep * random.random()
|
||||
logging.warning('connection to the bettercap endpoint failed...')
|
||||
pwnagotchi.restart("AUTO")
|
||||
|
||||
def run(self, command, verbose_errors=True):
|
||||
while True:
|
||||
try:
|
||||
r = requests.post("%s/session" % self.url, auth=self.auth, json={'cmd': command})
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
sleep_time = min_sleep + max_sleep*random.random()
|
||||
logging.warning("[bettercap] can't run my request... connection to the bettercap endpoint failed...")
|
||||
logging.warning('[bettercap] retrying run in {} sec'.format(sleep_time))
|
||||
sleep(sleep_time)
|
||||
else:
|
||||
break
|
||||
|
||||
return decode(r, verbose_errors=verbose_errors)
|
231
pwnagotchi/defaults.toml
Normal file
231
pwnagotchi/defaults.toml
Normal file
@ -0,0 +1,231 @@
|
||||
main.name = "pwnagotchi"
|
||||
main.lang = "en"
|
||||
main.whitelist = [
|
||||
"EXAMPLE_NETWORK",
|
||||
"ANOTHER_EXAMPLE_NETWORK",
|
||||
"fo:od:ba:be:fo:od",
|
||||
"fo:od:ba"
|
||||
]
|
||||
main.confd = "/etc/pwnagotchi/conf.d/"
|
||||
main.custom_plugin_repos = [
|
||||
"https://github.com/jayofelony/pwnagotchi-torch-plugins/archive/master.zip",
|
||||
"https://github.com/tisboyo/pwnagotchi-pisugar2-plugin/archive/master.zip",
|
||||
"https://github.com/nullm0ose/pwnagotchi-plugin-pisugar3/archive/master.zip",
|
||||
"https://github.com/Sniffleupagus/pwnagotchi_plugins/archive/master.zip",
|
||||
"https://github.com/NeonLightning/pwny/archive/master.zip",
|
||||
"https://github.com/marbasec/UPSLite_Plugin_1_3/archive/master.zip"
|
||||
]
|
||||
|
||||
main.custom_plugins = "/usr/local/share/pwnagotchi/custom-plugins/"
|
||||
|
||||
main.plugins.auto-update.enabled = true
|
||||
main.plugins.auto-update.install = true
|
||||
main.plugins.auto-update.interval = 1
|
||||
|
||||
main.plugins.bt-tether.enabled = false
|
||||
|
||||
main.plugins.bt-tether.devices.android-phone.enabled = false
|
||||
main.plugins.bt-tether.devices.android-phone.search_order = 1
|
||||
main.plugins.bt-tether.devices.android-phone.mac = ""
|
||||
main.plugins.bt-tether.devices.android-phone.ip = "192.168.44.44"
|
||||
main.plugins.bt-tether.devices.android-phone.netmask = 24
|
||||
main.plugins.bt-tether.devices.android-phone.interval = 1
|
||||
main.plugins.bt-tether.devices.android-phone.scantime = 10
|
||||
main.plugins.bt-tether.devices.android-phone.max_tries = 10
|
||||
main.plugins.bt-tether.devices.android-phone.share_internet = false
|
||||
main.plugins.bt-tether.devices.android-phone.priority = 1
|
||||
|
||||
main.plugins.bt-tether.devices.ios-phone.enabled = false
|
||||
main.plugins.bt-tether.devices.ios-phone.search_order = 2
|
||||
main.plugins.bt-tether.devices.ios-phone.mac = ""
|
||||
main.plugins.bt-tether.devices.ios-phone.ip = "172.20.10.6"
|
||||
main.plugins.bt-tether.devices.ios-phone.netmask = 24
|
||||
main.plugins.bt-tether.devices.ios-phone.interval = 5
|
||||
main.plugins.bt-tether.devices.ios-phone.scantime = 20
|
||||
main.plugins.bt-tether.devices.ios-phone.max_tries = 0
|
||||
main.plugins.bt-tether.devices.ios-phone.share_internet = false
|
||||
main.plugins.bt-tether.devices.ios-phone.priority = 999
|
||||
|
||||
main.plugins.fix_services.enabled = true
|
||||
|
||||
main.plugins.gdrivesync.enabled = false
|
||||
main.plugins.gdrivesync.backupfiles = ['']
|
||||
main.plugins.gdrivesync.backup_folder = "PwnagotchiBackups"
|
||||
main.plugin.gdrivesync.interval = 1
|
||||
|
||||
main.plugins.gpio_buttons.enabled = false
|
||||
|
||||
main.plugins.gps.enabled = false
|
||||
main.plugins.gps.speed = 19200
|
||||
main.plugins.gps.device = "/dev/ttyUSB0" # for GPSD: "localhost:2947"
|
||||
|
||||
main.plugins.grid.enabled = true
|
||||
main.plugins.grid.report = true
|
||||
|
||||
main.plugins.logtail.enabled = false
|
||||
main.plugins.logtail.max-lines = 10000
|
||||
|
||||
main.plugins.memtemp.enabled = false
|
||||
main.plugins.memtemp.scale = "celsius"
|
||||
main.plugins.memtemp.orientation = "horizontal"
|
||||
|
||||
main.plugins.net-pos.enabled = false
|
||||
main.plugins.net-pos.api_key = "test"
|
||||
|
||||
main.plugins.onlinehashcrack.enabled = false
|
||||
main.plugins.onlinehashcrack.email = ""
|
||||
main.plugins.onlinehashcrack.dashboard = ""
|
||||
main.plugins.onlinehashcrack.single_files = false
|
||||
|
||||
main.plugins.pisugar2.enabled = false
|
||||
main.plugins.pisugar2.shutdown = 5
|
||||
main.plugins.pisugar2.sync_rtc_on_boot = false
|
||||
|
||||
main.plugins.session-stats.enabled = true
|
||||
main.plugins.session-stats.save_directory = "/var/tmp/pwnagotchi/sessions/"
|
||||
|
||||
main.plugins.ups_hat_c.enabled = false
|
||||
main.plugins.ups_hat_c.label_on = true # show BAT label or just percentage
|
||||
main.plugins.ups_hat_c.shutdown = 5 # battery percent at which the device will turn off
|
||||
main.plugins.ups_hat_c.bat_x_coord = 140
|
||||
main.plugins.ups_hat_c.bat_y_coord = 0
|
||||
|
||||
main.plugins.ups_lite.enabled = false
|
||||
main.plugins.ups_lite.shutdown = 2
|
||||
|
||||
main.plugins.webcfg.enabled = true
|
||||
|
||||
main.plugins.webgpsmap.enabled = false
|
||||
|
||||
main.plugins.wigle.enabled = false
|
||||
main.plugins.wigle.api_key = ""
|
||||
main.plugins.wigle.donate = false
|
||||
|
||||
main.plugins.wpa-sec.enabled = false
|
||||
main.plugins.wpa-sec.api_key = ""
|
||||
main.plugins.wpa-sec.api_url = "https://wpa-sec.stanev.org"
|
||||
main.plugins.wpa-sec.download_results = false
|
||||
|
||||
main.iface = "wlan0mon"
|
||||
main.mon_start_cmd = "/usr/bin/monstart"
|
||||
main.mon_stop_cmd = "/usr/bin/monstop"
|
||||
main.mon_max_blind_epochs = 50
|
||||
main.no_restart = false
|
||||
|
||||
main.filter = ""
|
||||
|
||||
main.log.path = "/var/log/pwnagotchi.log"
|
||||
main.log.rotation.enabled = true
|
||||
main.log.rotation.size = "10M"
|
||||
|
||||
ai.enabled = true
|
||||
ai.path = "/root/brain.nn"
|
||||
ai.laziness = 0.1
|
||||
ai.epochs_per_episode = 50
|
||||
|
||||
ai.params.gamma = 0.99
|
||||
ai.params.n_steps = 1
|
||||
ai.params.vf_coef = 0.25
|
||||
ai.params.ent_coef = 0.01
|
||||
ai.params.max_grad_norm = 0.5
|
||||
ai.params.learning_rate = 0.001
|
||||
ai.params.verbose = 1
|
||||
|
||||
personality.advertise = true
|
||||
personality.deauth = true
|
||||
personality.associate = true
|
||||
personality.channels = []
|
||||
personality.min_rssi = -200
|
||||
personality.ap_ttl = 120
|
||||
personality.sta_ttl = 300
|
||||
personality.recon_time = 30
|
||||
personality.max_inactive_scale = 2
|
||||
personality.recon_inactive_multiplier = 2
|
||||
personality.hop_recon_time = 10
|
||||
personality.min_recon_time = 5
|
||||
personality.max_interactions = 3
|
||||
personality.max_misses_for_recon = 5
|
||||
personality.excited_num_epochs = 10
|
||||
personality.bored_num_epochs = 15
|
||||
personality.sad_num_epochs = 25
|
||||
personality.bond_encounters_factor = 20000
|
||||
personality.throttle_a = 0.4
|
||||
personality.throttle_d = 0.9
|
||||
|
||||
personality.clear_on_exit = true # clear display when shutting down cleanly
|
||||
|
||||
ui.fps = 0.0
|
||||
ui.font.name = "DejaVuSansMono" # for japanese: fonts-japanese-gothic
|
||||
ui.font.size_offset = 0 # will be added to the font size
|
||||
|
||||
ui.faces.look_r = "( ⚆_⚆)"
|
||||
ui.faces.look_l = "(☉_☉ )"
|
||||
ui.faces.look_r_happy = "( ◕‿◕)"
|
||||
ui.faces.look_l_happy = "(◕‿◕ )"
|
||||
ui.faces.sleep = "(⇀‿‿↼)"
|
||||
ui.faces.sleep2 = "(≖‿‿≖)"
|
||||
ui.faces.awake = "(◕‿‿◕)"
|
||||
ui.faces.bored = "(-__-)"
|
||||
ui.faces.intense = "(°▃▃°)"
|
||||
ui.faces.cool = "(⌐■_■)"
|
||||
ui.faces.happy = "(•‿‿•)"
|
||||
ui.faces.excited = "(ᵔ◡◡ᵔ)"
|
||||
ui.faces.grateful = "(^‿‿^)"
|
||||
ui.faces.motivated = "(☼‿‿☼)"
|
||||
ui.faces.demotivated = "(≖__≖)"
|
||||
ui.faces.smart = "(✜‿‿✜)"
|
||||
ui.faces.lonely = "(ب__ب)"
|
||||
ui.faces.sad = "(╥☁╥ )"
|
||||
ui.faces.angry = "(-_-')"
|
||||
ui.faces.friend = "(♥‿‿♥)"
|
||||
ui.faces.broken = "(☓‿‿☓)"
|
||||
ui.faces.debug = "(#__#)"
|
||||
ui.faces.upload = "(1__0)"
|
||||
ui.faces.upload1 = "(1__1)"
|
||||
ui.faces.upload2 = "(0__1)"
|
||||
ui.faces.png = false
|
||||
ui.faces.position_x = 0
|
||||
ui.faces.position_y = 34
|
||||
|
||||
ui.web.enabled = true
|
||||
ui.web.address = "::" # listening on both ipv4 and ipv6 - switch to 0.0.0.0 to listen on just ipv4
|
||||
ui.web.username = "changeme"
|
||||
ui.web.password = "changeme"
|
||||
ui.web.origin = ""
|
||||
ui.web.port = 8080
|
||||
ui.web.on_frame = ""
|
||||
|
||||
ui.display.enabled = false
|
||||
ui.display.rotation = 180
|
||||
ui.display.type = "waveshare_4"
|
||||
|
||||
bettercap.handshakes = "/root/handshakes"
|
||||
bettercap.silence = [
|
||||
"ble.device.new",
|
||||
"ble.device.lost",
|
||||
"ble.device.disconnected",
|
||||
"ble.device.connected",
|
||||
"ble.device.service.discovered",
|
||||
"ble.device.characteristic.discovered",
|
||||
"wifi.client.new",
|
||||
"wifi.client.lost",
|
||||
"wifi.client.probe",
|
||||
"wifi.ap.new",
|
||||
"wifi.ap.lost",
|
||||
"mod.started"
|
||||
]
|
||||
|
||||
fs.memory.enabled = true
|
||||
fs.memory.mounts.log.enabled = true
|
||||
fs.memory.mounts.log.mount = "/var/log/"
|
||||
fs.memory.mounts.log.size = "50M"
|
||||
fs.memory.mounts.log.sync = 60
|
||||
fs.memory.mounts.log.zram = true
|
||||
fs.memory.mounts.log.rsync = true
|
||||
|
||||
fs.memory.mounts.data.enabled = true
|
||||
fs.memory.mounts.data.mount = "/var/tmp/pwnagotchi"
|
||||
fs.memory.mounts.data.size = "10M"
|
||||
fs.memory.mounts.data.sync = 3600
|
||||
fs.memory.mounts.data.zram = true
|
||||
fs.memory.mounts.data.rsync = true
|
190
pwnagotchi/fs/__init__.py
Normal file
190
pwnagotchi/fs/__init__.py
Normal file
@ -0,0 +1,190 @@
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
import contextlib
|
||||
import shutil
|
||||
import _thread
|
||||
import logging
|
||||
|
||||
from time import sleep
|
||||
from distutils.dir_util import copy_tree
|
||||
|
||||
mounts = list()
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def ensure_write(filename, mode='w'):
|
||||
path = os.path.dirname(filename)
|
||||
fd, tmp = tempfile.mkstemp(dir=path)
|
||||
|
||||
with os.fdopen(fd, mode) as f:
|
||||
yield f
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
|
||||
os.replace(tmp, filename)
|
||||
|
||||
|
||||
def size_of(path):
|
||||
"""
|
||||
Calculate the sum of all the files in path
|
||||
"""
|
||||
total = 0
|
||||
for root, _, files in os.walk(path):
|
||||
for f in files:
|
||||
total += os.path.getsize(os.path.join(root, f))
|
||||
return total
|
||||
|
||||
|
||||
def is_mountpoint(path):
|
||||
"""
|
||||
Checks if path is mountpoint
|
||||
"""
|
||||
return os.system(f"mountpoint -q {path}") == 0
|
||||
|
||||
|
||||
def setup_mounts(config):
|
||||
"""
|
||||
Sets up all the configured mountpoints
|
||||
"""
|
||||
global mounts
|
||||
fs_cfg = config['fs']['memory']
|
||||
if not fs_cfg['enabled']:
|
||||
return
|
||||
|
||||
for name, options in fs_cfg['mounts'].items():
|
||||
if not options['enabled']:
|
||||
continue
|
||||
logging.debug("[FS] Trying to setup mount %s (%s)", name, options['mount'])
|
||||
size,unit = re.match(r"(\d+)([a-zA-Z]+)", options['size']).groups()
|
||||
target = os.path.join('/run/pwnagotchi/disk/', os.path.basename(options['mount']))
|
||||
|
||||
is_mounted = is_mountpoint(target)
|
||||
logging.debug("[FS] %s is %s mounted", options['mount'],
|
||||
"already" if is_mounted else "not yet")
|
||||
|
||||
m = MemoryFS(
|
||||
options['mount'],
|
||||
target,
|
||||
size=options['size'],
|
||||
zram=options['zram'],
|
||||
zram_disk_size=f"{int(size)*2}{unit}",
|
||||
rsync=options['rsync'])
|
||||
|
||||
if not is_mounted:
|
||||
if not m.mount():
|
||||
logging.debug(f"Error while mounting {m.mountpoint}")
|
||||
continue
|
||||
|
||||
if not m.sync(to_ram=True):
|
||||
logging.debug(f"Error while syncing to {m.mountpoint}")
|
||||
m.umount()
|
||||
continue
|
||||
|
||||
interval = int(options['sync'])
|
||||
if interval:
|
||||
logging.debug("[FS] Starting thread to sync %s (interval: %d)",
|
||||
options['mount'], interval)
|
||||
_thread.start_new_thread(m.daemonize, (interval,))
|
||||
else:
|
||||
logging.debug("[FS] Not syncing %s, because interval is 0",
|
||||
options['mount'])
|
||||
|
||||
mounts.append(m)
|
||||
|
||||
|
||||
class MemoryFS:
|
||||
@staticmethod
|
||||
def zram_install():
|
||||
if not os.path.exists("/sys/class/zram-control"):
|
||||
logging.debug("[FS] Installing zram")
|
||||
return os.system("modprobe zram") == 0
|
||||
return True
|
||||
|
||||
|
||||
@staticmethod
|
||||
def zram_dev():
|
||||
logging.debug("[FS] Adding zram device")
|
||||
return open("/sys/class/zram-control/hot_add", "rt").read().strip("\n")
|
||||
|
||||
|
||||
def __init__(self, mount, disk, size="40M",
|
||||
zram=True, zram_alg="lz4", zram_disk_size="100M",
|
||||
zram_fs_type="ext4", rsync=True):
|
||||
self.mountpoint = mount
|
||||
self.disk = disk
|
||||
self.size = size
|
||||
self.zram = zram
|
||||
self.zram_alg = zram_alg
|
||||
self.zram_disk_size = zram_disk_size
|
||||
self.zram_fs_type = zram_fs_type
|
||||
self.zdev = None
|
||||
self.rsync = True
|
||||
self._setup()
|
||||
|
||||
|
||||
def _setup(self):
|
||||
if self.zram and MemoryFS.zram_install():
|
||||
# setup zram
|
||||
self.zdev = MemoryFS.zram_dev()
|
||||
open(f"/sys/block/zram{self.zdev}/comp_algorithm", "wt").write(self.zram_alg)
|
||||
open(f"/sys/block/zram{self.zdev}/disksize", "wt").write(self.zram_disk_size)
|
||||
open(f"/sys/block/zram{self.zdev}/mem_limit", "wt").write(self.size)
|
||||
logging.debug("[FS] Creating fs (type: %s)", self.zram_fs_type)
|
||||
os.system(f"mke2fs -t {self.zram_fs_type} /dev/zram{self.zdev} >/dev/null 2>&1")
|
||||
|
||||
# ensure mountpoints exist
|
||||
if not os.path.exists(self.disk):
|
||||
logging.debug("[FS] Creating %s", self.disk)
|
||||
os.makedirs(self.disk)
|
||||
|
||||
if not os.path.exists(self.mountpoint):
|
||||
logging.debug("[FS] Creating %s", self.mountpoint)
|
||||
os.makedirs(self.mountpoint)
|
||||
|
||||
|
||||
def daemonize(self, interval=60):
|
||||
logging.debug("[FS] Daemonized...")
|
||||
while True:
|
||||
self.sync()
|
||||
sleep(interval)
|
||||
|
||||
|
||||
def sync(self, to_ram=False):
|
||||
source, dest = (self.disk, self.mountpoint) if to_ram else (self.mountpoint, self.disk)
|
||||
needed, actually_free = size_of(source), shutil.disk_usage(dest)[2]
|
||||
if actually_free >= needed:
|
||||
logging.debug("[FS] Syncing %s -> %s", source,dest)
|
||||
if self.rsync:
|
||||
os.system(f"rsync -aXv --inplace --no-whole-file --delete-after {source}/ {dest}/ >/dev/null 2>&1")
|
||||
else:
|
||||
copy_tree(source, dest, preserve_symlinks=True)
|
||||
os.system("sync")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def mount(self):
|
||||
if os.system(f"mount --bind {self.mountpoint} {self.disk}"):
|
||||
return False
|
||||
|
||||
if os.system(f"mount --make-private {self.disk}"):
|
||||
return False
|
||||
|
||||
if self.zram and self.zdev is not None:
|
||||
if os.system(f"mount -t {self.zram_fs_type} -o nosuid,noexec,nodev,user=pwnagotchi /dev/zram{self.zdev} {self.mountpoint}/"):
|
||||
return False
|
||||
else:
|
||||
if os.system(f"mount -t tmpfs -o nosuid,noexec,nodev,mode=0755,size={self.size} pwnagotchi {self.mountpoint}/"):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def umount(self):
|
||||
if os.system(f"umount -l {self.mountpoint}"):
|
||||
return False
|
||||
|
||||
if os.system(f"umount -l {self.disk}"):
|
||||
return False
|
||||
return True
|
101
pwnagotchi/google/cmd.py
Normal file
101
pwnagotchi/google/cmd.py
Normal file
@ -0,0 +1,101 @@
|
||||
# Handles the commandline stuff
|
||||
|
||||
import pydrive2
|
||||
from pydrive2.auth import GoogleAuth
|
||||
import logging
|
||||
import os
|
||||
|
||||
|
||||
def add_parsers(subparsers):
|
||||
"""
|
||||
Adds the plugins subcommand to a given argparse.ArgumentParser
|
||||
"""
|
||||
#subparsers = parser.add_subparsers()
|
||||
# pwnagotchi google
|
||||
parser_google = subparsers.add_parser('google')
|
||||
google_subparsers = parser_google.add_subparsers(dest='googlecmd')
|
||||
|
||||
# pwnagotchi google auth
|
||||
parser_google_auth = google_subparsers.add_parser('login', help='Login to Google')
|
||||
|
||||
# pwnagotchi google refresh token
|
||||
parser_google_refresh = google_subparsers.add_parser('refresh', help="Refresh Google authentication token")
|
||||
return subparsers
|
||||
|
||||
|
||||
def used_google_cmd(args):
|
||||
"""
|
||||
Checks if the plugins subcommand was used
|
||||
"""
|
||||
return hasattr(args, 'googlecmd')
|
||||
|
||||
|
||||
def handle_cmd(args):
|
||||
"""
|
||||
Parses the arguments and does the thing the user wants
|
||||
"""
|
||||
if args.googlecmd == 'login':
|
||||
return auth()
|
||||
elif args.googlecmd == 'refresh':
|
||||
return refresh()
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def auth():
|
||||
# start authentication process
|
||||
user_input = input("By completing these steps you give pwnagotchi access to your personal Google Drive!\n"
|
||||
"Personal credentials will be stored only locally for automated verification in the future.\n"
|
||||
"No one else but you have access to these.\n"
|
||||
"Do you agree? \n\n[y(es)/n(o)]\n"
|
||||
"Answer: ")
|
||||
if user_input.lower() in ('y', 'yes'):
|
||||
if not os.path.exists("/root/client_secrets.json"):
|
||||
logging.error("client_secrets.json not found in /root. Please RTFM!")
|
||||
return 0
|
||||
try:
|
||||
gauth = GoogleAuth(settings_file="/root/settings.yaml")
|
||||
print(gauth.GetAuthUrl())
|
||||
user_input = input("Please copy this URL into a browser, "
|
||||
"complete the verification and then copy/paste the code from addressbar.\n\n"
|
||||
"Code: ")
|
||||
gauth.Auth(user_input)
|
||||
gauth.SaveCredentialsFile("/root/credentials.json")
|
||||
except Exception as e:
|
||||
logging.error(f"Error: {e}")
|
||||
return 0
|
||||
|
||||
|
||||
def refresh():
|
||||
# refresh token for x amount of time (seconds)
|
||||
gauth = GoogleAuth(settings_file="/root/settings.yaml")
|
||||
try:
|
||||
# Try to load saved client credentials
|
||||
gauth.LoadCredentialsFile("/root/credentials.json")
|
||||
if gauth.access_token_expired:
|
||||
if gauth.credentials is not None:
|
||||
try:
|
||||
# Refresh the token
|
||||
gauth.Refresh()
|
||||
print("Succesfully refresh access token ..")
|
||||
except pydrive2.auth.RefreshError:
|
||||
print(gauth.GetAuthUrl())
|
||||
user_input = input("Please copy this URL into a browser, "
|
||||
"complete the verification and then copy/paste the code from addressbar.\n\n"
|
||||
"Code: ")
|
||||
gauth.Auth(user_input)
|
||||
else:
|
||||
print(gauth.GetAuthUrl())
|
||||
user_input = input("Please copy this URL into a browser, "
|
||||
"complete the verification and then copy/paste the code from addressbar.\n\n"
|
||||
"Code: ")
|
||||
gauth.Auth(user_input)
|
||||
except pydrive2.auth.InvalidCredentialsError:
|
||||
print(gauth.GetAuthUrl())
|
||||
user_input = input("Please copy this URL into a browser, "
|
||||
"complete the verification and then copy/paste the code from addressbar.\n\n"
|
||||
"Code: ")
|
||||
gauth.Auth(user_input)
|
||||
gauth.SaveCredentialsFile("/root/credentials.json")
|
||||
gauth.Authorize()
|
||||
print("No refresh required ..")
|
||||
return 0
|
131
pwnagotchi/grid.py
Normal file
131
pwnagotchi/grid.py
Normal file
@ -0,0 +1,131 @@
|
||||
import subprocess
|
||||
import requests
|
||||
import json
|
||||
import logging
|
||||
|
||||
import pwnagotchi
|
||||
|
||||
# pwngrid-peer is running on port 8666
|
||||
API_ADDRESS = "http://127.0.0.1:8666/api/v1"
|
||||
|
||||
|
||||
def is_connected():
|
||||
try:
|
||||
# check DNS
|
||||
host = 'https://api.opwngrid.xyz/api/v1/uptime'
|
||||
r = requests.get(host, headers=None, timeout=(30.0, 60.0))
|
||||
if r.json().get('isUp'):
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def call(path, obj=None):
|
||||
url = '%s%s' % (API_ADDRESS, path)
|
||||
if obj is None:
|
||||
r = requests.get(url, headers=None, timeout=(30.0, 60.0))
|
||||
elif isinstance(obj, dict):
|
||||
r = requests.post(url, headers=None, json=obj, timeout=(30.0, 60.0))
|
||||
else:
|
||||
r = requests.post(url, headers=None, data=obj, timeout=(30.0, 60.0))
|
||||
|
||||
if r.status_code != 200:
|
||||
raise Exception("(status %d) %s" % (r.status_code, r.text))
|
||||
return r.json()
|
||||
|
||||
|
||||
def advertise(enabled=True):
|
||||
return call("/mesh/%s" % 'true' if enabled else 'false')
|
||||
|
||||
|
||||
def set_advertisement_data(data):
|
||||
return call("/mesh/data", obj=data)
|
||||
|
||||
|
||||
def get_advertisement_data():
|
||||
return call("/mesh/data")
|
||||
|
||||
|
||||
def memory():
|
||||
return call("/mesh/memory")
|
||||
|
||||
|
||||
def peers():
|
||||
return call("/mesh/peers")
|
||||
|
||||
|
||||
def closest_peer():
|
||||
all = peers()
|
||||
return all[0] if len(all) else None
|
||||
|
||||
|
||||
def update_data(last_session):
|
||||
brain = {}
|
||||
try:
|
||||
with open('/root/brain.json') as fp:
|
||||
brain = json.load(fp)
|
||||
except:
|
||||
pass
|
||||
enabled = [name for name, options in pwnagotchi.config['main']['plugins'].items() if
|
||||
'enabled' in options and options['enabled']]
|
||||
language = pwnagotchi.config['main']['lang']
|
||||
ai = pwnagotchi.config['ai']['enabled']
|
||||
|
||||
data = {
|
||||
'ai': ai,
|
||||
'session': {
|
||||
'duration': last_session.duration,
|
||||
'epochs': last_session.epochs,
|
||||
'train_epochs': last_session.train_epochs,
|
||||
'avg_reward': last_session.avg_reward,
|
||||
'min_reward': last_session.min_reward,
|
||||
'max_reward': last_session.max_reward,
|
||||
'deauthed': last_session.deauthed,
|
||||
'associated': last_session.associated,
|
||||
'handshakes': last_session.handshakes,
|
||||
'peers': last_session.peers,
|
||||
},
|
||||
'uname': subprocess.getoutput("uname -a"),
|
||||
'brain': brain,
|
||||
'version': pwnagotchi.__version__,
|
||||
'build': "Pwnagotchi-Torch by Jayofelony",
|
||||
'plugins': enabled,
|
||||
'language': language,
|
||||
'bettercap': subprocess.getoutput("bettercap -version").split(".\n\n")[1],
|
||||
'opwngrid': subprocess.getoutput("pwngrid -version")
|
||||
}
|
||||
|
||||
logging.debug("updating grid data: %s" % data)
|
||||
|
||||
call("/data", data)
|
||||
|
||||
|
||||
def report_ap(essid, bssid):
|
||||
try:
|
||||
call("/report/ap", {
|
||||
'essid': essid,
|
||||
'bssid': bssid,
|
||||
})
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.exception("error while reporting ap %s(%s)" % (essid, bssid))
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def inbox(page=1, with_pager=False):
|
||||
obj = call("/inbox?p=%d" % page)
|
||||
return obj["messages"] if not with_pager else obj
|
||||
|
||||
|
||||
def inbox_message(id):
|
||||
return call("/inbox/%d" % int(id))
|
||||
|
||||
|
||||
def mark_message(id, mark):
|
||||
return call("/inbox/%d/%s" % (int(id), str(mark)))
|
||||
|
||||
|
||||
def send_message(to, message):
|
||||
return call("/unit/%s/inbox" % to, message.encode('utf-8'))
|
71
pwnagotchi/identity.py
Normal file
71
pwnagotchi/identity.py
Normal file
@ -0,0 +1,71 @@
|
||||
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, view=None):
|
||||
self.path = path
|
||||
self.priv_path = os.path.join(path, "id_rsa")
|
||||
self.priv_key = None
|
||||
self.pub_path = "%s.pub" % self.priv_path
|
||||
self.pub_key = None
|
||||
self.fingerprint_path = os.path.join(path, "fingerprint")
|
||||
self._view = view
|
||||
|
||||
if not os.path.exists(self.path):
|
||||
os.makedirs(self.path)
|
||||
|
||||
while True:
|
||||
# first time, generate new keys
|
||||
if not os.path.exists(self.priv_path) or not os.path.exists(self.pub_path):
|
||||
self._view.on_keys_generation()
|
||||
logging.info("generating %s ..." % self.priv_path)
|
||||
os.system("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.
|
||||
try:
|
||||
with open(self.priv_path) as fp:
|
||||
self.priv_key = RSA.importKey(fp.read())
|
||||
|
||||
with open(self.pub_path) as fp:
|
||||
self.pub_key = RSA.importKey(fp.read())
|
||||
self.pub_key_pem = self.pub_key.exportKey('PEM').decode("ascii")
|
||||
# python is special
|
||||
if 'RSA PUBLIC KEY' not in self.pub_key_pem:
|
||||
self.pub_key_pem = self.pub_key_pem.replace('PUBLIC KEY', 'RSA PUBLIC KEY')
|
||||
|
||||
pem_ascii = self.pub_key_pem.encode("ascii")
|
||||
|
||||
self.pub_key_pem_b64 = base64.b64encode(pem_ascii).decode("ascii")
|
||||
self.fingerprint = hashlib.sha256(pem_ascii).hexdigest()
|
||||
|
||||
with open(self.fingerprint_path, 'w+t') as fp:
|
||||
fp.write(self.fingerprint)
|
||||
|
||||
# no exception, keys loaded correctly.
|
||||
self._view.on_starting()
|
||||
return
|
||||
|
||||
except Exception as e:
|
||||
# if we're here, loading the keys broke something ...
|
||||
logging.exception("error loading keys, maybe corrupted, deleting and regenerating ...")
|
||||
try:
|
||||
os.remove(self.priv_path)
|
||||
os.remove(self.pub_path)
|
||||
except:
|
||||
pass
|
||||
|
||||
def sign(self, message):
|
||||
hasher = SHA256.new(message.encode("ascii"))
|
||||
signer = PKCS1_PSS.new(self.priv_key, saltLen=16)
|
||||
signature = signer.sign(hasher)
|
||||
signature_b64 = base64.b64encode(signature).decode("ascii")
|
||||
return signature, signature_b64
|
BIN
pwnagotchi/locale/aa/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/aa/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
257
pwnagotchi/locale/aa/LC_MESSAGES/voice.po
Normal file
257
pwnagotchi/locale/aa/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,257 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: Afar\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr ""
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr ""
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr ""
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr ""
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr ""
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr ""
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good night."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Downloading from {name} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Made >999 new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
|
||||
msgid "hours"
|
||||
msgstr ""
|
||||
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "seconds"
|
||||
msgstr ""
|
||||
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
|
||||
msgid "second"
|
||||
msgstr ""
|
BIN
pwnagotchi/locale/ab/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/ab/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
257
pwnagotchi/locale/ab/LC_MESSAGES/voice.po
Normal file
257
pwnagotchi/locale/ab/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,257 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: Abkhazian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr ""
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr ""
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr ""
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr ""
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr ""
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr ""
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good night."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Downloading from {name} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Made >999 new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
|
||||
msgid "hours"
|
||||
msgstr ""
|
||||
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "seconds"
|
||||
msgstr ""
|
||||
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
|
||||
msgid "second"
|
||||
msgstr ""
|
BIN
pwnagotchi/locale/ae/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/ae/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
257
pwnagotchi/locale/ae/LC_MESSAGES/voice.po
Normal file
257
pwnagotchi/locale/ae/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,257 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: Avestan\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr ""
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr ""
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr ""
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr ""
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr ""
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr ""
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good night."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Downloading from {name} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Made >999 new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
|
||||
msgid "hours"
|
||||
msgstr ""
|
||||
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "seconds"
|
||||
msgstr ""
|
||||
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
|
||||
msgid "second"
|
||||
msgstr ""
|
BIN
pwnagotchi/locale/af/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/af/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
259
pwnagotchi/locale/af/LC_MESSAGES/voice.po
Normal file
259
pwnagotchi/locale/af/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,259 @@
|
||||
# Afrikaans translation of pwnagotchi.
|
||||
# Copyright (C) 2020.
|
||||
# This file is distributed under the same license as the pwnagotchi package.
|
||||
# FIRST AUTHOR MatthewNunu https://github.com/MatthewNunu, 2020.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 1.5.3\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-11-16 21:51+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: MatthewNunu https://github.com/MatthewNunu\n"
|
||||
"Language-Team: \n"
|
||||
"Language: Afrikaans\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr "ZzzzZZzzzzZzzz"
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "Hi, ek is Pwnagotchi! Begin tans ..."
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Nuwe dag, nuwe jag, nuwe pwns!"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Hack die wêreld!"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr "AI gereed."
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr "Die neurale netwerk is gereed."
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr "Genereer wagwoord, moenie afskakel nie ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Haai, kanaal {channel} is gratis! Jou AP sal dankie sê."
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr "Lees laaste sessie logs ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr "Ek het {lines_so_far} tot dusver gelees ..."
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Ek's verveeld ..."
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Kom ons gaan vir 'n loopie!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Dit is die beste dag van my lewe!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Slegte dag :/"
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Ek's baie verveeld ..."
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Ek's baie hartseer ..."
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr "Ek's hartseer ..."
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr "Los my uit ..."
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr "Ek is kwaad vir jou!"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Ek leef my beste lewe!"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Ek pwn daarom is ek."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Soveel netwerke!!!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Ek het soveel pret!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "My misdaad is dié van nuuskierigheid ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr "Hallo {name}! Lekker om jou te ontmoet."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr "Yo {name}! Sup?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr "Haai {name} hoe gaan dit?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr "{name}} is naby!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr "Uhm ... totsiens {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name} is weg ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Oeps ... {name} is weg."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "{name} gemis!"
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr "Gemis!"
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr "Goeie vriende is 'n seën!"
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr "Ek is lief vir my vriende!"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Niemand wil met my speel nie ..."
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Ek voel so alleen ..."
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Waar is almal?!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Slaap vir {secs}s ..."
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr "Zzzzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr "ZzzZzzz ({secs}s)"
|
||||
|
||||
msgid "Good night."
|
||||
msgstr "Goeie nag."
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr "Zzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Wag vir {secs}s ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Kyk rond ({secs}s)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Haai {what} kom ons wees vriende!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr "Assosieer met {what}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr "Yo {what}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Net besluit dat {mac} geen WiFi nodig het nie!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr "Deauthenticating {mac}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "Kickbanning {mac}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Koel, ons het {num} nuwe handdruk gekry!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "Jy het {count} nuwe boodskap!"
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Oops, iets het verkeerd gegaan ... Herlaai ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr "Laai data op na {to} ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Downloading from {name} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "{num} stasies geskop\n"
|
||||
|
||||
msgid "Made >999 new friends\n"
|
||||
msgstr "Meer as >999 nuwe vriende gemaak\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "{num} nuwe vriende gemaak\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "Het {num} handdrukke gekry\n"
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr "Ontmoet 1 eweknie"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "Ontmoet {num} eweknieë"
|
||||
|
||||
#, 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 ""
|
||||
"Ek het vir {duration} gepwn en het {deauthed} kliënte geskop! Ek het ook "
|
||||
"{associated} nuwe vriende ontmoet en het {handshakes} handdrukke geëet! "
|
||||
"#pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
|
||||
msgid "hours"
|
||||
msgstr "ure"
|
||||
|
||||
msgid "minutes"
|
||||
msgstr "minute"
|
||||
|
||||
msgid "seconds"
|
||||
msgstr "sekondes"
|
||||
|
||||
msgid "hour"
|
||||
msgstr "uur"
|
||||
|
||||
msgid "minute"
|
||||
msgstr "minuut"
|
||||
|
||||
msgid "second"
|
||||
msgstr "tweede"
|
BIN
pwnagotchi/locale/ak/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/ak/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
257
pwnagotchi/locale/ak/LC_MESSAGES/voice.po
Normal file
257
pwnagotchi/locale/ak/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,257 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: Akan\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr ""
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr ""
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr ""
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr ""
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr ""
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr ""
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good night."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Downloading from {name} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Made >999 new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
|
||||
msgid "hours"
|
||||
msgstr ""
|
||||
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "seconds"
|
||||
msgstr ""
|
||||
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
|
||||
msgid "second"
|
||||
msgstr ""
|
BIN
pwnagotchi/locale/am/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/am/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
257
pwnagotchi/locale/am/LC_MESSAGES/voice.po
Normal file
257
pwnagotchi/locale/am/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,257 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: Amharic\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr ""
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr ""
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr ""
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr ""
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr ""
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr ""
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good night."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Downloading from {name} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Made >999 new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
|
||||
msgid "hours"
|
||||
msgstr ""
|
||||
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "seconds"
|
||||
msgstr ""
|
||||
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
|
||||
msgid "second"
|
||||
msgstr ""
|
BIN
pwnagotchi/locale/an/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/an/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
257
pwnagotchi/locale/an/LC_MESSAGES/voice.po
Normal file
257
pwnagotchi/locale/an/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,257 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: Aragonese\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr ""
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr ""
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr ""
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr ""
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr ""
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr ""
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good night."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Downloading from {name} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Made >999 new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
|
||||
msgid "hours"
|
||||
msgstr ""
|
||||
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "seconds"
|
||||
msgstr ""
|
||||
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
|
||||
msgid "second"
|
||||
msgstr ""
|
BIN
pwnagotchi/locale/ar/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/ar/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
257
pwnagotchi/locale/ar/LC_MESSAGES/voice.po
Normal file
257
pwnagotchi/locale/ar/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,257 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: Arabic\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr ""
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr ""
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr ""
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr ""
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr ""
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr ""
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good night."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Downloading from {name} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Made >999 new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
|
||||
msgid "hours"
|
||||
msgstr ""
|
||||
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "seconds"
|
||||
msgstr ""
|
||||
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
|
||||
msgid "second"
|
||||
msgstr ""
|
BIN
pwnagotchi/locale/as/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/as/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
257
pwnagotchi/locale/as/LC_MESSAGES/voice.po
Normal file
257
pwnagotchi/locale/as/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,257 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: Assamese\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr ""
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr ""
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr ""
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr ""
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr ""
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr ""
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good night."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Downloading from {name} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Made >999 new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
|
||||
msgid "hours"
|
||||
msgstr ""
|
||||
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "seconds"
|
||||
msgstr ""
|
||||
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
|
||||
msgid "second"
|
||||
msgstr ""
|
BIN
pwnagotchi/locale/av/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/av/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
257
pwnagotchi/locale/av/LC_MESSAGES/voice.po
Normal file
257
pwnagotchi/locale/av/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,257 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: Avaric\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr ""
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr ""
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr ""
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr ""
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr ""
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr ""
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good night."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Downloading from {name} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Made >999 new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
|
||||
msgid "hours"
|
||||
msgstr ""
|
||||
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "seconds"
|
||||
msgstr ""
|
||||
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
|
||||
msgid "second"
|
||||
msgstr ""
|
BIN
pwnagotchi/locale/ay/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/ay/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
257
pwnagotchi/locale/ay/LC_MESSAGES/voice.po
Normal file
257
pwnagotchi/locale/ay/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,257 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: Aymara\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr ""
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr ""
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr ""
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr ""
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr ""
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr ""
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good night."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Downloading from {name} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Made >999 new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
|
||||
msgid "hours"
|
||||
msgstr ""
|
||||
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "seconds"
|
||||
msgstr ""
|
||||
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
|
||||
msgid "second"
|
||||
msgstr ""
|
BIN
pwnagotchi/locale/az/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/az/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
257
pwnagotchi/locale/az/LC_MESSAGES/voice.po
Normal file
257
pwnagotchi/locale/az/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,257 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: Azerbaijani\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr ""
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr ""
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr ""
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr ""
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr ""
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr ""
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good night."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Downloading from {name} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Made >999 new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
|
||||
msgid "hours"
|
||||
msgstr ""
|
||||
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "seconds"
|
||||
msgstr ""
|
||||
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
|
||||
msgid "second"
|
||||
msgstr ""
|
BIN
pwnagotchi/locale/ba/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/ba/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
257
pwnagotchi/locale/ba/LC_MESSAGES/voice.po
Normal file
257
pwnagotchi/locale/ba/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,257 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: Bashkir\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr ""
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr ""
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr ""
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr ""
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr ""
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr ""
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good night."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Downloading from {name} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Made >999 new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
|
||||
msgid "hours"
|
||||
msgstr ""
|
||||
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "seconds"
|
||||
msgstr ""
|
||||
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
|
||||
msgid "second"
|
||||
msgstr ""
|
BIN
pwnagotchi/locale/be/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/be/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
268
pwnagotchi/locale/be/LC_MESSAGES/voice.po
Normal file
268
pwnagotchi/locale/be/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,268 @@
|
||||
# Pwnagotchi Belarusian translation.
|
||||
# Copyright (C) 2019
|
||||
# This file is distributed under the same license as the pwnagotchi package.
|
||||
# First author <https://github.com/andreifinski>, 2023
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Pwnagotchi Belarusian translation v 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-11-16 21:51+0100\n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: <https://github.com/andreifinski>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: by_BY\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
|
||||
"n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
|
||||
"X-Generator: Poedit 2.2.4\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
"X-Poedit-Basepath: .\n"
|
||||
"X-Poedit-SearchPath-0: voice.po\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr "ХрррРРррррРррр"
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "Вітаю, я Pwnagotchi! Пачынаем!"
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Новы дзень, новае паляванне, новыя ўзломы!"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Узламай гэту Планету!"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr "A.I. гатовы."
|
||||
|
||||
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 "Гэй, канал {channel} вольны! Ваш пункт доступу скажа дзякуй."
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr "Чытаю логі апошняй сэсіі..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr "Чытаю {lines_so_far} радкоў логаў..."
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Мне сумна …"
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Хадзем пагуляем!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Лепшы дзень у маім жыцці!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Дзень проста гаўно :/"
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Мне вельмі нудна …"
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Мне вельмі сумна …"
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr "Мне сумна"
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr "Пакінь мяне ў спакоі..."
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr "Я злы на цябе!"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Жыву поўным жыццём!"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Я ўзломваю, таму я існую."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Так шмат сетак!!!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Мне так весела!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "Маё злачынства - гэта цікаўнасць…"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr "Прывітанне, {name}! Прыемна пазнаёміцца."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr "Гэй {name}! Як маешся?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr "Мэта {name} побач!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr "Хм … да пабачэння {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name} сышоў"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Ай-яй {name} сышоў."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "{name} страціў!"
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr "Прамахнуўся!"
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr "Добрыя сябры - гэта шчасце!"
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr "Я люблю сваіх сяброў!"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Ніхто не жадае са мной гуляць ..."
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Я такі самотны…"
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Дзе ўсе?!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Драмлю {secs}с …"
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr "Хрррр..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr "ХррРрр.. ({secs}c)"
|
||||
|
||||
msgid "Good night."
|
||||
msgstr "Дабранач."
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr "Хрр"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Чакаем {secs}c …"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Аглядаюся вакол ({secs}с)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Гэй, {what} давай сябраваць!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr "Звязваюся з {what}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr "Ёў {what}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Проста вырашыў, што {mac} не патрэбен WiFi! Гы-гы :)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr "Дэаўтэнтыфікацыя {mac}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "Пінаю {mac}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Крута, мы атрымалі {num} новы поціск рукі!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "Крута, мы атрымалі {count} новы поціск рукі!"
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Ой, нешта пайшло не так … Перазагружаюся …"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Downloading from {name} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "Пнуў {num} станцыю\n"
|
||||
|
||||
msgid "Made >999 new friends\n"
|
||||
msgstr "Атрымаў >999 новых сяброў\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "Атрымаў {num} новых сяброў\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "Атрымаў {num} поціскаў рукі\n"
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr "Сустрэўся адзін знаёмы"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "Сустрэліся {num} знаёмых"
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
"Я ўзломваў {duration} и штурхаў {deauthed} кліентаў! Таксама сустрэў "
|
||||
"{associated} новых сяброў и з'еў {handshakes} поціскаў рукі! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
|
||||
msgid "hours"
|
||||
msgstr "гадзін"
|
||||
|
||||
msgid "minutes"
|
||||
msgstr "хвілін"
|
||||
|
||||
msgid "seconds"
|
||||
msgstr ""
|
||||
|
||||
msgid "hour"
|
||||
msgstr "гадзіна"
|
||||
|
||||
msgid "minute"
|
||||
msgstr "хвіліну"
|
||||
|
||||
msgid "second"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
#~ msgid "Unit {name} is nearby! {name}"
|
||||
#~ msgstr "Мэта {name} побач!"
|
BIN
pwnagotchi/locale/bg/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/bg/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
260
pwnagotchi/locale/bg/LC_MESSAGES/voice.po
Normal file
260
pwnagotchi/locale/bg/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,260 @@
|
||||
# pwnagotchi voice data.
|
||||
# Copyright (C) 2019
|
||||
# This file is distributed under the same license as the pwnagotchi package.
|
||||
# FIRST AUTHOR <https://github.com/georgikoemdzhiev>, 2019.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-11-16 21:51+0100\n"
|
||||
"PO-Revision-Date: 2019-10-23 20:56+0200\n"
|
||||
"Last-Translator: Georgi Koemdzhiev <https://github.com/georgikoemdzhiev>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: bulgarian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "Здравей, аз съм Pwnagotchi! Стартиране ..."
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Нов ден, нов лов, нови pwns!"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Хакни планетата!"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr "AI готов."
|
||||
|
||||
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 "Здравей, канал {channel} е свободен! твоя AP ще каже благодаря."
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Скучно ми е ..."
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Хайда да се поразходим!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Това е най-добрият ден в живота ми!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Тъп ден :/"
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Супер много ми е скучно ..."
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Много съм тъжен ..."
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr "Тъжен съм"
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr "Остави ме на мира ..."
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Живота ми е фантастичен!"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Аз живея за да pwn-вам."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Толкова много мрежи!!!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Толкова много се забавлявам!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "Моето престъпление е това че съм любопитен ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr "Здравей {name}! Приятно ми е да се запознаем."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr "Устройство {name} е наблизо!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr "Ами ... довиждане {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name} изчезна ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Упс ... {name} изчезна."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "{name} загубен!"
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr "Загубен!"
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr "Добрите приятели са благословия!"
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr "Обичам приятелите си!"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Никой не иска да си играе с мен ..."
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Чувствам се толкова самотен ..."
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Къде са всички?!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Заспивам за {secs} секунди ..."
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr "Zzzzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr "ZzzZzzz ({secs}s)"
|
||||
|
||||
msgid "Good night."
|
||||
msgstr "Лека нощ."
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr "Zzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Чакам {secs} секунди ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Оглеждам се ({secs}секунди)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Хей {what} нека станем приятели!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr "Свръзване с {what}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr "Ей {what}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Реших, че {mac} не се нуждае от WiFi!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr "Неудостоверяване на {mac}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "Ритам и прогонвам {mac}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Супер, имаме {num} нови handshake{plural}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr "Имате {count} нови съобщения!"
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr "Упс, нещо се обърка ... Рестартиране ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Downloading from {name} ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "Отхвърлих {num} станции\n"
|
||||
|
||||
#
|
||||
msgid "Made >999 new friends\n"
|
||||
msgstr "Направих >999 нови приятели\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "Направих {num} нови приятели\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "Имам {num} handshakes\n"
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr "Срещнах 1 връстник"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "Срещнах {num} връстници"
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
"Аз pwn-вах за {duration} и отхвърлих {deauthed} clients! Също така срещнах "
|
||||
"{associated} нови приятели и изядох {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
|
||||
msgid "hours"
|
||||
msgstr "часове"
|
||||
|
||||
msgid "minutes"
|
||||
msgstr "минути"
|
||||
|
||||
msgid "seconds"
|
||||
msgstr "секунди"
|
||||
|
||||
msgid "hour"
|
||||
msgstr "час"
|
||||
|
||||
msgid "minute"
|
||||
msgstr "минута"
|
||||
|
||||
msgid "second"
|
||||
msgstr "секунда"
|
BIN
pwnagotchi/locale/bi/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/bi/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user