Compare commits

..

10 Commits

Author SHA1 Message Date
fb7ef7b1dd Update build
Signed-off-by: jayofelony <oudshoorn.jeroen@gmail.com>
2024-11-17 21:56:42 +01:00
28ff1429ac Update build
Signed-off-by: jayofelony <oudshoorn.jeroen@gmail.com>
2024-11-17 21:52:58 +01:00
68941abc08 Update build
Signed-off-by: jayofelony <oudshoorn.jeroen@gmail.com>
2024-11-17 21:00:28 +01:00
a7379d18e6 Update build
Signed-off-by: jayofelony <oudshoorn.jeroen@gmail.com>
2024-11-17 20:42:34 +01:00
f9318ef27a Merge remote-tracking branch 'origin/master' 2024-11-17 20:07:59 +01:00
cedb3d35ff Update build
Signed-off-by: jayofelony <oudshoorn.jeroen@gmail.com>
2024-11-17 20:07:55 +01:00
4861a0c08b Merge pull request #211 from SeanDuggan/master
Icelandic Translations for IS Locale
2024-10-02 19:25:34 +02:00
766463280d Updated missing charset line
Signed-off-by: SeanDuggan <duggse01@risk.regn.net>
2024-10-01 16:48:39 +01:00
29e278da57 Icelandic Translations
Signed-off-by: SeanDuggan <duggse01@risk.regn.net>
2024-09-30 22:16:06 +01:00
7e6d198c9b Update build
Signed-off-by: jayofelony <oudshoorn.jeroen@gmail.com>
2024-09-19 07:37:15 +02:00
328 changed files with 5414 additions and 5948 deletions

36
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,36 @@
<!--- Provide a general summary of the issue in the Title above -->
## Expected Behaviour
<!--- If you're describing a bug, tell us what should happen -->
<!--- If you're suggesting a change/improvement, tell us how it should work -->
## Current Behaviour
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
## Possible Solution
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
<!--- or ideas how to implement the addition or change -->
## Steps to Reproduce (for bugs)
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. Include code to reproduce, if relevant -->
1.
2.
3.
4.
## Context
<!--- How has this issue affected you? What are you trying to accomplish? -->
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
## Your Environment
- [ ] You're using the official images
- [ ] You're using a raspberry pi 0
- [ ] You're using a supported LCD
<!--- Include as many relevant details about the environment you experienced the bug in -->

View File

@ -1,55 +0,0 @@
name: Bug Report
description: File a bug report.
title: "[Bug]: "
labels: ["bug", "triage"]
projects: ["pwnagotchi"]
assignees:
- jayofelony
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: input
id: contact
attributes:
label: Contact Details
description: How can we get in touch with you if we need more info?
placeholder: ex. email@example.com
validations:
required: false
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Also tell us, what did you expect to happen?
placeholder: Tell us what you see!
value: "A bug happened!"
validations:
required: true
- type: dropdown
id: version
attributes:
label: Version
description: What version of our software are you running?
options:
- 2.9.3
- 2.9.3-2
- 2.9.4
default: 0
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell
- type: checkboxes
id: terms
attributes:
label: Code of Conduct
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/jayofelony/pwnagotchi/blob/noai/CODE_OF_CONDUCT.md).
options:
- label: I agree to follow this project's Code of Conduct
required: true

32
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,32 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG]"
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. ...
2. ...
3. ...
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Environment (please complete the following information):**
- Pwnagotchi version
- OS version
- Type of hardware
- Any additional hardware used
**Additional context**
Add any other context about the problem here.

View File

@ -1,55 +0,0 @@
name: Bug Report
description: File a bug report.
title: "[Bug]: "
labels: ["bug", "triage"]
projects: ["pwnagotchi"]
assignees:
- jayofelony
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: input
id: contact
attributes:
label: Contact Details
description: How can we get in touch with you if we need more info?
placeholder: ex. email@example.com
validations:
required: false
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Also tell us, what did you expect to happen?
placeholder: Tell us what you see!
value: "A bug happened!"
validations:
required: true
- type: dropdown
id: version
attributes:
label: Version
description: What version of our software are you running?
options:
- 2.9.3
- 2.9.3-2
- 2.9.4
default: 0
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell
- type: checkboxes
id: terms
attributes:
label: Code of Conduct
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/jayofelony/pwnagotchi/blob/noai/CODE_OF_CONDUCT.md).
options:
- label: I agree to follow this project's Code of Conduct
required: true

View File

@ -1,59 +0,0 @@
name: Pull Request Template
description: Template for submitting pull requests.
body:
- type: markdown
attributes:
value: |
## Description
Provide a detailed description of your changes here.
- type: textarea
id: motivation_and_context
attributes:
label: Motivation and Context
description: Why is this change necessary? What problem does it solve?
placeholder: Add details here. Link to open issues if applicable.
validations:
required: true
- type: checkboxes
id: issue_proposal
attributes:
label: Did you raise an issue for this change? ([required](https://github.com/evilsocket/pwnagotchi/blob/master/CONTRIBUTING.md))
options:
- label: "Yes, I raised an issue."
required: true
- type: textarea
id: testing
attributes:
label: How has this been tested?
description: Provide a detailed description of how your changes were tested.
placeholder: Add details about your testing environment, the tests you ran, and the impact of your changes.
validations:
required: true
- type: checkboxes
id: types_of_changes
attributes:
label: What types of changes does this Pull Request introduce?
options:
- label: "Bug fix (non-breaking change that fixes an issue)"
- label: "New feature (non-breaking change that adds functionality)"
- label: "Breaking change (fix or feature that affects existing functionality)"
validations:
required: true
- type: checkboxes
id: checklist
attributes:
label: Verify the following
options:
- label: "My code follows the code style of this project."
- label: "My change requires a change to the documentation."
- label: "I have updated the documentation accordingly."
- label: "I have read the [CONTRIBUTION](https://github.com/evilsocket/pwnagotchi/blob/master/CONTRIBUTING.md) guide."
- label: "I have signed off my commits with `git commit -s`."
validations:
required: true

55
.github/workflows/publish.yml vendored Normal file
View File

@ -0,0 +1,55 @@
name: Publish
on:
workflow_dispatch:
jobs:
build:
name: ${{ matrix.name }}
runs-on: ubuntu-latest
strategy:
matrix:
include:
- name: "Raspberry Pi 32-bit"
id: "32bit"
- name: "Raspberry Pi 64-bit"
id: "64bit"
steps:
- uses: actions/checkout@v4
with:
path: publish/build
- name: Extract version from file
id: get_version
run: |
VERSION=$(cut -d "'" -f2 < publish/build/pwnagotchi/_version.py)
echo "VERSION=$VERSION" >> $GITHUB_ENV
- name: Install qemu dependencies
run: sudo apt update && sudo apt install qemu-user-static qemu-utils xz-utils -y
- name: Build ${{ matrix.name }} img file
run: cd publish/build; ls -la .; pwd; make packer; make ${{ matrix.id }}
- name: Change name of .img.xz to add version
run: |
sudo chown runner:docker "pwnagotchi-${{ matrix.id }}.img"
mv "pwnagotchi-${{ matrix.id }}.img" "pwnagotchi-${{ env.VERSION }}-${{ matrix.id }}.img"
- name: PiShrink
run: |
wget https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh
chmod +x pishrink.sh
sudo mv pishrink.sh /usr/local/bin
sudo pishrink.sh -aZ "pwnagotchi-${{ env.VERSION }}-${{ matrix.id }}.img"
- name: Release
uses: softprops/action-gh-release@v2
with:
prerelease: false
make_latest: true
tag_name: v${{ env.VERSION }}
name: Pwnagotchi v${{ env.VERSION }}
files: pwnagotchi-${{ env.VERSION }}-${{ matrix.id }}.img.xz
generate_release_notes: true

1
.gitignore vendored
View File

@ -1 +1,2 @@
*.pyc

1
.idea/pwnagotchi.iml generated
View File

@ -2,7 +2,6 @@
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.12 (pwnagotchi)" jdkType="Python SDK" />

View File

@ -1,6 +1,9 @@
exclude *.pyc .gitignore MANIFEST.in
exclude *.pyc .DS_Store .gitignore MANIFEST.in
include setup.py
include distribute_setup.py
include README.md
include LICENSE
recursive-include bin *
recursive-include pwnagotchi *.py
recursive-include pwnagotchi *.yml
recursive-include pwnagotchi *.*

View File

@ -1,3 +1,33 @@
PACKER_VERSION := 1.11.0
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: packer image
update_langs:
@for lang in pwnagotchi/locale/*/; do\
echo "updating language: $$lang ..."; \
@ -9,3 +39,24 @@ compile_langs:
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 -o /tmp/packer.zip -d /tmp
sudo mv /tmp/packer /usr/bin/packer
image: packer
export LC_ALL=en_GB.UTF-8
cd builder && sudo /usr/bin/packer init combined.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" combined.json.pkr.hcl
32bit: packer
export LC_ALL=en_GB.UTF-8
cd builder && sudo /usr/bin/packer init raspberrypi32.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" raspberrypi32.json.pkr.hcl
64bit: packer
export LC_ALL=en_GB.UTF-8
cd builder && sudo /usr/bin/packer init raspberrypi64.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" raspberrypi64.json.pkr.hcl
clean:
- rm -rf /tmp/packer*
- rm -rf /tmp/LICENSE.txt

View File

@ -5,36 +5,36 @@ This is the main source for all forks:
**For installation docs check out the [wiki](https://github.com/jayofelony/pwnagotchi/wiki)!**
If you want to sponsor this project you can use GH Sponsor or cryptocurrency:
[GH Sponsor](https://github.com/sponsors/jayofelony)
Or send some ethereum: 0x33ceC4Abe80fDE460a924d596d4dE31Bc0767bb6
**Proudly partnering with [PiSugar](https://www.pisugar.com)!!**
---
[Pwnagotchi](https://pwnagotchi.org/) is a Raspberry Pi leveraging [bettercap](https://www.bettercap.org/) that survives from its surrounding Wi-Fi environment to maximize the crackable WPA key material it captures (either passively, or by performing authentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/),
[Pwnagotchi](https://pwnagotchi.org/) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding Wi-Fi environment to maximize the crackable WPA key material it captures (either passively, or by performing authentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/),
full and half WPA handshakes.
![ui](https://i.imgur.com/X68GXrn.png)
The "old" Pwnagotchi used to have AI to help it learn from its environment, but since then AI seemed to destabilize the Wi-Fi firmware. So I have chosen to remove the AI completely to give the Pwnagotchi more up-time and longer battery life when taking it on a walk.
Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning-based "AI" *(yawn)*, Pwnagotchi tunes [its parameters](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.toml) over time to **get better at pwning Wi-Fi things to** in the environments you expose it to.
Multiple units within close physical proximity can "talk" to each other, advertising their presence to each other by broadcasting custom information elements using a parasite protocol I've built on top of the existing dot11 standard.
More specifically, Pwnagotchi is using an [LSTM with MLP feature extractor](https://stable-baselines.readthedocs.io/en/master/modules/policies.html#stable_baselines.common.policies.MlpLstmPolicy) as its policy network for the [A2C agent](https://stable-baselines.readthedocs.io/en/master/modules/a2c.html). If you're unfamiliar with A2C, here is [a very good introductory explanation](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) (in comic form!) of the basic principles behind how Pwnagotchi learns. (You can read more about how Pwnagotchi learns in the [Usage](https://www.pwnagotchi.ai/usage/#training-the-ai) doc.)
**Keep in mind:** Unlike the usual RL simulations, Pwnagotchi learns over time. Time for a Pwnagotchi is measured in epochs; a single epoch can last from a few seconds to minutes, depending on how many access points and client stations are visible. Do not expect your Pwnagotchi to perform amazingly well at the very beginning, as it will be [exploring](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) several combinations of [key parameters](https://www.pwnagotchi.ai/usage/#training-the-ai) to determine ideal adjustments for pwning the particular environment you are exposing it to during its beginning epochs ... but ** listen to your Pwnagotchi when it tells you it's boring!** Bring it into novel Wi-Fi environments with you and have it observe new networks and capture new handshakes—and you'll see. :)
Multiple units within close physical proximity can "talk" to each other, advertising their presence to each other by broadcasting custom information elements using a parasite protocol I've built on top of the existing dot11 standard. Over time, two or more units trained together will learn to cooperate upon detecting each other's presence by dividing the available channels among them for optimal pwnage.
## Documentation
https://github.com/jayofelony/pwnagotchi/wiki
https://pwnagotchi.org
https://www.pwnagotchi.org
## Links
| &nbsp; | Official Links |
|-----------|----------------------------------------------------------|
|-----------|-------------------------------------------------------------|
| Website | [pwnagotchi.org](https://pwnagotchi.org/) |
| Chat | [discord](https://discord.gg/PGgnzFbz4M) |
| Forum | [discord.gg](https://discord.gg/PGgnzFbz4M) |
| Subreddit | [r/pwnagotchi](https://www.reddit.com/r/pwnagotchi/) |
## License

0
apt_packages.txt Normal file
View File

31
pwnagotchi/cli.py → bin/pwnagotchi Normal file → Executable file
View File

@ -1,3 +1,4 @@
#!/usr/bin/python3
import logging
import argparse
import time
@ -13,6 +14,7 @@ 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
@ -48,7 +50,6 @@ def pwnagotchi_cli():
logging.info("entering auto mode ...")
agent.mode = 'auto'
agent.last_session.parse(agent.view(), args.skip_session) # show stats in AUTO
agent.start()
while True:
@ -215,24 +216,20 @@ def pwnagotchi_cli():
"[Y/N] ")
if pwn_bluetooth.lower() in ('y', 'yes'):
f.write("main.plugins.bt-tether.enabled = true\n\n")
pwn_bluetooth_phone_name = input("What name uses your phone, check settings?\n\n")
if pwn_bluetooth_phone_name != "":
f.write(f"main.plugins.bt-tether.phone-name = \"{pwn_bluetooth_phone_name}\"\n")
pwn_bluetooth_device = input("What device do you use? android or ios?\n\n"
pwn_bluetooth_device = input("What device do you use? Android or iOS?\n\n"
"Device: ")
if pwn_bluetooth_device != "":
if pwn_bluetooth_device != "android" and pwn_bluetooth_device != "ios":
print("You have chosen an invalid device. Please start over.")
exit()
f.write(f"main.plugins.bt-tether.phone = \"{pwn_bluetooth_device.lower()}\"\n")
if pwn_bluetooth_device == "android":
f.write("main.plugins.bt-tether.ip = \"192.168.44.44\"\n")
elif pwn_bluetooth_device == "ios":
f.write("main.plugins.bt-tether.ip = \"172.20.10.6\"\n")
if pwn_bluetooth_device.lower() == "android":
f.write("main.plugins.bt-tether.devices.android-phone.enabled = true\n")
pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n"
"MAC: ")
if pwn_bluetooth_mac != "":
f.write(f"main.plugins.bt-tether.mac = \"{pwn_bluetooth_mac}\"\n")
f.write(f"main.plugins.bt-tether.devices.android-phone.mac = \"{pwn_bluetooth_mac}\"\n")
elif pwn_bluetooth_device.lower() == "ios":
f.write("main.plugins.bt-tether.devices.ios-phone.enabled = true\n")
pwn_bluetooth_mac = input("What is the bluetooth MAC of your device?\n\n"
"MAC: ")
if pwn_bluetooth_mac != "":
f.write(f"main.plugins.bt-tether.devices.ios-phone.mac = \"{pwn_bluetooth_mac}\"\n")
# set up display settings
pwn_display_enabled = input("Do you want to enable a display?\n\n"
"[Y/N]: ")
@ -254,9 +251,7 @@ def pwnagotchi_cli():
if pwn_bluetooth.lower() in ('y', 'yes'):
if pwn_bluetooth_device.lower == "android":
print("To visit the webui when connected with your phone, visit: http://192.168.44.44:8080\n"
"Be sure to run `sudo bluetoothctl` to set-up the bluetooth connection for the first time. And read the wiki step 4.\n"
"Your configuration is done, and I will restart in 5 seconds.")
elif pwn_bluetooth_device.lower == "ios":
print("To visit the webui when connected with your phone, visit: http://172.20.10.6:8080\n"
"Your configuration is done, and I will restart in 5 seconds.")
@ -327,7 +322,7 @@ def pwnagotchi_cli():
agent = Agent(view=display, config=config, keypair=KeyPair(view=display))
def usr1_handler(*unused):
logging.info('Received USR1 signal. Restart process ...')
logging.info('Received USR1 singal. Restart process ...')
agent._restart("MANU" if args.do_manual else "AUTO")
signal.signal(signal.SIGUSR1, usr1_handler)

View File

@ -0,0 +1,147 @@
packer {
required_plugins {
arm-image = {
source = "github.com/solo-io/arm-image"
version = ">= 0.0.1"
}
ansible = {
source = "github.com/hashicorp/ansible"
version = ">= 1.1.1"
}
}
}
variable "pwn_hostname" {
type = string
}
variable "pwn_version" {
type = string
}
source "arm-image" "rpi64-pwnagotchi" {
image_type = "raspberrypi"
iso_url = "https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-07-04/2024-07-04-raspios-bookworm-arm64-lite.img.xz"
iso_checksum = "sha256:43d150e7901583919e4eb1f0fa83fe0363af2d1e9777a5bb707d696d535e2599"
output_filename = "../../../pwnagotchi-64bit.img"
qemu_binary = "qemu-aarch64-static"
target_image_size = 19969908736
image_mounts = ["/boot/firmware","/"]
}
source "arm-image" "rpi32-pwnagotchi" {
image_type = "raspberrypi"
iso_url = "https://downloads.raspberrypi.com/raspios_lite_armhf/images/raspios_lite_armhf-2024-07-04/2024-07-04-raspios-bookworm-armhf-lite.img.xz"
iso_checksum = "sha256:df9c192d66d35e1ce67acde33a5b5f2b81ff02d2b986ea52f1f6ea211d646a1b"
output_filename = "../../../pwnagotchi-32bit.img"
qemu_binary = "qemu-arm-static"
qemu_args = ["-cpu", "arm1176"]
image_arch = "arm"
target_image_size = 19969908736
image_mounts = ["/boot/firmware","/"]
}
# 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-image.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 "shell" {
inline = ["mkdir -p /usr/local/src/pwnagotchi"]
}
provisioner "file" {
destination = "/usr/local/src/pwnagotchi/"
source = "../"
}
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 = "raspberrypi64.yml"
}
}
build {
name = "Raspberry Pi 32 Pwnagotchi"
sources = ["source.arm-image.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 = ["mkdir -p /usr/local/src/pwnagotchi"]
}
provisioner "file" {
destination = "/usr/local/src/pwnagotchi/"
source = "../"
}
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_file = "raspberrypi32.yml"
}
}

View File

@ -0,0 +1,5 @@
[main]
plugins=keyfile,ifupdown
[ifupdown]
managed=true

View File

@ -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 --wizard --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

View 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

View File

@ -0,0 +1,6 @@
# /etc/modules: kernel modules to load at boot time.
#
# This file contains the names of kernel modules that should be loaded
# at boot time, one per line. Lines beginning with "#" are ignored.
# Parameters can be specified after the module name.
i2c-dev

View File

@ -0,0 +1,2 @@
allow-hotplug eth0
iface eth0 inet dhcp

View File

@ -0,0 +1,2 @@
auto lo
iface lo inet loopback

View 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

View File

@ -0,0 +1,2 @@
allow-hotplug wlan0
iface wlan0 inet static

16
builder/data/etc/rc.local Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.
timedatectl set-ntp true
exit 0

View 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

View 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

View File

@ -0,0 +1,19 @@
[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
Restart=always
RestartSec=30
TasksMax=infinity
LimitNPROC=infinity
StandardOutput=null
StandardError=null
[Install]
WantedBy=multi-user.target

View 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 /etc/pwnagotchi/log/pwngrid-peer.log -iface wlan0mon
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,34 @@
#!/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 --wizard, to help set up a config.toml"
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/"

View File

View 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

View File

@ -0,0 +1,18 @@
#!/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_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

View 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/usr/bin/hdmioff Executable file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env bash
sudo /usr/bin/tvservice -o

2
builder/data/usr/bin/hdmion Executable file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env bash
sudo /usr/bin/tvservice -p

3
builder/data/usr/bin/monstart Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
source /usr/bin/pwnlib
start_monitor_interface

3
builder/data/usr/bin/monstop Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
source /usr/bin/pwnlib
stop_monitor_interface

View File

@ -0,0 +1,17 @@
#!/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/src/pwnagotchi/env/bin/pwnagotchi
systemctl restart bettercap
else
/usr/local/src/pwnagotchi/env/bin/pwnagotchi --manual
fi

178
builder/data/usr/bin/pwnlib Executable file
View File

@ -0,0 +1,178 @@
#!/usr/bin/env bash
# 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
}

View File

@ -0,0 +1,82 @@
packer {
required_plugins {
arm-image = {
source = "github.com/solo-io/arm-image"
version = ">= 0.0.1"
}
ansible = {
source = "github.com/hashicorp/ansible"
version = ">= 1.1.1"
}
}
}
variable "pwn_hostname" {
type = string
}
variable "pwn_version" {
type = string
}
source "arm-image" "rpi32-pwnagotchi" {
image_type = "raspberrypi"
iso_url = "https://downloads.raspberrypi.com/raspios_lite_armhf/images/raspios_lite_armhf-2024-07-04/2024-07-04-raspios-bookworm-armhf-lite.img.xz"
iso_checksum = "sha256:df9c192d66d35e1ce67acde33a5b5f2b81ff02d2b986ea52f1f6ea211d646a1b"
output_filename = "../../../pwnagotchi-32bit.img"
qemu_binary = "qemu-arm-static"
qemu_args = ["-cpu", "arm1176"]
image_arch = "arm"
image_mounts = ["/boot/firmware","/"]
target_image_size = 19969908736
}
build {
name = "Raspberry Pi 32 Pwnagotchi"
sources = ["source.arm-image.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 "shell" {
inline = ["mkdir -p /usr/local/src/pwnagotchi"]
}
provisioner "file" {
destination = "/usr/local/src/pwnagotchi/"
source = "../"
}
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_file = "raspberrypi32.yml"
}
}

560
builder/raspberrypi32.yml Normal file
View File

@ -0,0 +1,560 @@
---
- hosts:
- 127.0.0.1
gather_facts: true
become: true
vars:
kernel:
min: "6.6"
full: "6.6.31+rpt-rpi-v6"
pwnagotchi:
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi', true) }}"
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"
branch: "lite" # or master
bettercap:
source: "https://github.com/jayofelony/bettercap.git"
branch: "lite" # or master
pwngrid:
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+gita8e7c98-cp311-cp311-linux_armv6l.whl"
url: "https://github.com/Sniffleupagus/Torch4Pizero/releases/download/py0torch-bookworm-2024-05/torch-2.1.0a0+gita8e7c98-cp311-cp311-linux_armv6l.whl"
torchvision:
wheel: "torchvision-0.16.0+fbb4cc5-cp311-cp311-linux_armv6l.whl"
url: "https://github.com/Sniffleupagus/Torch4Pizero/releases/download/py0torch-bookworm-2024-05/torchvision-0.16.0+fbb4cc5-cp311-cp311-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-dbg
- libpcap0.8-dev
remove:
- nfs-common
- triggerhappy
install:
- aircrack-ng
- autoconf
- bison
- bluez
- bluez-tools
- build-essential
- curl
- dphys-swapfile
- fbi
- firmware-atheros
- firmware-brcm80211
- firmware-libertas
- firmware-misc-nonfree
- firmware-realtek
- flex
- g++
- gawk
- gcc-arm-none-eabi
- git
- libatlas-base-dev
- libc6-dev
- libcpuinfo-dev
- libcurl-ocaml-dev
- libdbus-1-dev
- libdbus-glib-1-dev
- libfl-dev
- libgmp3-dev
- libnetfilter-queue-dev
- libopenblas-dev # https://stackoverflow.com/questions/14570011/explain-why-numpy-should-not-be-imported-from-source-directory
- libopenjp2-7
- libpcap-dev
#- libraspberrypi-bin ## seems to be provided by raspi-utils now
- libraspberrypi-dev
- libraspberrypi-doc
- libraspberrypi0
- libsleef-dev
- libssl-dev
- libssl-ocaml-dev
- libtool
- libusb-1.0-0-dev
- make
- ntp
- pkg-config
- python3-dev
- python3-pip
- python3-protobuf
- python3-setuptools
- python3-smbus
- qpdf
- raspberrypi-kernel-headers
- rsync
- tcpdump
- texinfo
- unzip
- wget
- wl
- xxd
- zlib1g-dev
environment:
ARCHFLAGS: "-arch armv6l"
tasks:
# First we install packages
- name: install packages
apt:
name: "{{ packages.apt.install }}"
state: latest
update_cache: yes
install_recommends: no
- name: update pip3, setuptools, wheel
shell: "python3 -m pip install --upgrade pip setuptools wheel --break-system-packages"
args:
executable: /bin/bash
chdir: /usr/local/src
- name: install 32bit torch
shell: "python3 -m pip install {{ packages.torch.url }} {{ packages.torchvision.url }} --break-system-packages"
args:
executable: /bin/bash
environment:
QEMU_UNAME: "{{ kernel.full }}"
ARCHFLAGS: "-arch armv6l"
# Now we set up /boot/firmware
- name: Create pi user
copy:
dest: /boot/firmware/userconf
content: |
pi:$5$733Efsksay$SEFUKemv8FaNAu6X4GUfxdSzSDh6PbpOcdtNe5b7Nt0
- name: enable ssh on boot
file:
path: /boot/firmware/ssh
state: touch
- name: remove current rc.local
file:
path: /etc/rc.local
state: absent
- 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: setup /boot/firmware/config.txt
blockinfile:
path: /boot/firmware/config.txt
insertafter: EOF
block: |
dtparam=i2c1=on
dtparam=i2c_arm=on
dtparam=spi=on
gpu_mem=1
dtoverlay=dwc2
#dtoverlay=disable-wifi
enable_uart=1
[pi0]
dtoverlay=spi0-2cs
#dtoverlay=disable-wifi
- 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
# Installing nexmon
- 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/
environment:
QEMU_UNAME: "{{ kernel.full }}"
ARCHFLAGS: "-arch armv6l"
- 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 armv6l"
- 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 modified driver
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 armv6l"
- 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
- 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 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: create /usr/local/share/pwnagotchi/ folder
file:
path: /usr/local/share/pwnagotchi/
state: directory
- name: Create custom plugin directory
file:
path: /usr/local/share/pwnagotchi/custom-plugins/
state: directory
- name: remove pwnagotchi folder
file:
state: absent
path: /usr/local/src/pwnagotchi
##########################################
#
# pwngrid, bettercap
#
##########################################
- name: Install go-1.21
unarchive:
src: https://go.dev/dl/go1.22.3.linux-armv6l.tar.gz
dest: /usr/local
remote_src: yes
register: golang
- name: Update .bashrc for go-1.21
blockinfile:
dest: /etc/profile
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 }}"
version: "{{ packages.bettercap.branch }} "
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: clone bettercap caplets
git:
repo: "{{ packages.caplets.source }}"
version: "{{ packages.caplets.branch }}"
dest: /tmp/caplets
register: capletsgit
- name: install bettercap caplets
make:
chdir: /tmp/caplets
target: install
when: capletsgit.changed
- 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_4"
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 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: install rpi-sys-mods again?
apt:
state: present
name: raspberrypi-sys-mods
update_cache: yes
install_recommends: no
- name: clean apt cache
apt:
autoclean: true
when: removed.changed
handlers:
- name: reload systemd services
systemd:
daemon_reload: yes
when: enabled.changed

View File

@ -0,0 +1,84 @@
packer {
required_plugins {
arm-image = {
source = "github.com/solo-io/arm-image"
version = ">= 0.0.1"
}
ansible = {
source = "github.com/hashicorp/ansible"
version = ">= 1.1.1"
}
}
}
variable "pwn_hostname" {
type = string
}
variable "pwn_version" {
type = string
}
source "arm-image" "rpi64-pwnagotchi" {
image_type = "raspberrypi"
iso_url = "https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-07-04/2024-07-04-raspios-bookworm-arm64-lite.img.xz"
iso_checksum = "sha256:43d150e7901583919e4eb1f0fa83fe0363af2d1e9777a5bb707d696d535e2599"
output_filename = "../../../pwnagotchi-64bit.img"
qemu_binary = "qemu-aarch64-static"
target_image_size = 19969908736
image_mounts = ["/boot/firmware","/"]
}
# 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-image.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 "shell" {
inline = ["mkdir -p /usr/local/src/pwnagotchi"]
}
provisioner "file" {
destination = "/usr/local/src/pwnagotchi/"
source = "../"
}
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 = "raspberrypi64.yml"
}
}

631
builder/raspberrypi64.yml Normal file
View File

@ -0,0 +1,631 @@
---
- hosts:
- 127.0.0.1
gather_facts: true
become: true
vars:
kernel:
min: "6.6"
full: "6.6.31+rpt-rpi-v8"
full_pi5: "6.6.31+rpt-rpi-2712"
pwnagotchi:
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi', true) }}"
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"
branch: "lite" # or master
bettercap:
source: "https://github.com/jayofelony/bettercap.git"
url: "https://github.com/jayofelony/bettercap/releases/download/2.32.4/bettercap-2.32.4.zip"
branch: "lite" # or master
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:
- dhpys-swapfile
- nfs-common
- triggerhappy
install:
- aircrack-ng
- autoconf
- bison
- bluez
- bluez-tools
- build-essential
- curl
- dphys-swapfile
- fbi
- firmware-atheros
- firmware-brcm80211
- firmware-libertas
- firmware-misc-nonfree
- firmware-realtek
- flex
- g++
- gawk
- gcc-arm-none-eabi
- git
- libc6-dev
- libcurl-ocaml-dev
- libdbus-1-dev
- libdbus-glib-1-dev
- libfl-dev
- libgmp3-dev
- libnetfilter-queue-dev
- libpcap-dev
#- libraspberrypi-bin ## seems to be provided by raspi-utils now
- libraspberrypi-dev
- libraspberrypi-doc
- libraspberrypi0
- libssl-dev
- libssl-ocaml-dev
- libtool
- libusb-1.0-0-dev
- make
- ntp
- pkg-config
- python3-dev
- python3-pip
- python3-setuptools
- python3-smbus
- qpdf
- raspberrypi-kernel-headers
- raspberrypi-sys-mods
- rsync
- tcpdump
- texinfo
- unzip
- wget
- wl
- xxd
- zlib1g-dev
environment:
ARCHFLAGS: "-arch aarch64"
tasks:
# First we install packages
- name: install packages
apt:
name: "{{ packages.apt.install }}"
state: latest
update_cache: yes
install_recommends: no
- name: update pip3, setuptools, wheel
shell: "python3 -m pip install --upgrade pip setuptools wheel --break-system-packages"
args:
executable: /bin/bash
chdir: /usr/local/src
- 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
# Now we set up /boot/firmware
- name: Create pi user
copy:
dest: /boot/firmware/userconf
content: |
pi:$5$733Efsksay$SEFUKemv8FaNAu6X4GUfxdSzSDh6PbpOcdtNe5b7Nt0
- name: enable ssh on boot
file:
path: /boot/firmware/ssh
state: touch
- name: remove current rc.local
file:
path: /etc/rc.local
state: absent
- 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: setup /boot/firmware/config.txt
blockinfile:
path: /boot/firmware/config.txt
insertafter: EOF
block: |
dtparam=i2c1=on
dtparam=i2c_arm=on
dtparam=spi=on
gpu_mem=1
dtoverlay=dwc2
#dtoverlay=disable-wifi
enable_uart=1
[pi02w]
dtoverlay=spi0-2cs
#dtoverlay=disable-wifi
[pi3]
dtoverlay=spi0-2cs
#dtoverlay=disable-wifi
[pi4]
dtoverlay=spi0-2cs
#dtoverlay=disable-wifi
[pi5]
dtoverlay=spi0-2cs
#dtoverlay=disable-wifi
- 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
# Installing nexmon
- 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: 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: 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 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: 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
- /usr/lib/firmware/brcm/BCM43430A1.raspberrypi,model-zero-2-w.hcd
- 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 config directory
file:
path: /etc/pwnagotchi/conf.d/
state: directory
- name: create /usr/local/share/pwnagotchi/ folder
file:
path: /usr/local/share/pwnagotchi/
state: directory
- name: Create custom plugin directory
file:
path: /usr/local/share/pwnagotchi/custom-plugins/
state: directory
- name: Install go-1.21
unarchive:
src: https://go.dev/dl/go1.22.3.linux-arm64.tar.gz
dest: /usr/local
remote_src: yes
register: golang
- name: Update .bashrc for go-1.21
blockinfile:
dest: /etc/profile
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 }}"
version: "{{ packages.bettercap.branch }}"
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 }}"
version: "{{ packages.caplets.branch }}"
dest: /tmp/caplets
register: capletsgit
- name: install bettercap caplets
make:
chdir: /tmp/caplets
target: install
when: capletsgit.changed
- 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_4"
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 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

View File

@ -9,6 +9,7 @@ _name = None
config = None
_cpu_stats = {}
def set_name(new_name):
if new_name is None:
return

View File

@ -1 +1 @@
__version__ = '2.9.4'
__version__ = '2.9.2'

View File

@ -16,11 +16,12 @@ 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):
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'],
@ -30,6 +31,7 @@ class Agent(Client, Automata, AsyncAdvertiser):
"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
@ -129,6 +131,7 @@ class Agent(Client, Automata, AsyncAdvertiser):
time.sleep(1)
def start(self):
self.start_ai()
self._wait_bettercap()
self.setup_events()
self.set_starting()

74
pwnagotchi/ai/__init__.py Normal file
View File

@ -0,0 +1,74 @@
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))
# remove invalid ai.parameters leftover from tensor_flow, if present
for key in [ 'alpha', 'epsilon', 'lr_schedule' ]:
if key in config['params']:
logging.info("Removing legacy ai parameter %s" % key);
del config['params'][key]
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

View 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
View File

@ -0,0 +1,148 @@
import logging
import gymnasium as gym
from gymnasium 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.multi_discrete.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)

View File

@ -0,0 +1,30 @@
from gymnasium 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)

View File

@ -1,28 +1,27 @@
import pwnagotchi.mesh.wifi as wifi
range: tuple[float, float] = (-.7, 1.02)
fuck_zero: float = 1e-20
range = (-.7, 1.02)
fuck_zero = 1e-20
class RewardFunction(object):
def __call__(self, epoch_n: float, state: dict[str, float]) -> float:
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
tot_epochs: float = epoch_n + fuck_zero
tot_interactions: float = max(state['num_deauths'] + state['num_associations'], state['num_handshakes']) + fuck_zero
tot_channels: int = wifi.NumChannels
h = state['num_handshakes'] / tot_interactions
a = .2 * (state['active_for_epochs'] / tot_epochs)
c = .1 * (state['num_hops'] / tot_channels)
h: float = state['num_handshakes'] / tot_interactions
a: float = .2 * (state['active_for_epochs'] / tot_epochs)
c: float = .1 * (state['num_hops'] / tot_channels)
b: float = -.3 * (state['blind_for_epochs'] / tot_epochs)
m: float = -.3 * (state['missed_interactions'] / tot_interactions)
i: float = -.2 * (state['inactive_for_epochs'] / tot_epochs)
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: float = state['sad_for_epochs'] if state['sad_for_epochs'] >= 5 else 0
_bored: float = state['bored_for_epochs'] if state['bored_for_epochs'] >= 5 else 0
s: float = -.2 * (_sad / tot_epochs)
l: float = -.1 * (_bored / tot_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

198
pwnagotchi/ai/train.py Normal file
View File

@ -0,0 +1,198 @@
# 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, ())
threading.Thread(target=self._ai_worker, args=(), name="AI Worker", daemon=True).start()
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
View 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]

View File

@ -2,7 +2,6 @@ import logging
import pwnagotchi.plugins as plugins
from pwnagotchi.ai.epoch import Epoch
import os
# basic mood system
@ -137,6 +136,7 @@ class Automata(object):
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()

View File

@ -9,25 +9,42 @@ main.whitelist = [
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",
"https://github.com/wpa-2/Pwnagotchi-Plugins/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-tune.enabled = true
main.plugins.auto-update.enabled = false
main.plugins.auto-update.install = false
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.phone-name = "" # name as shown on the phone i.e. "Pwnagotchi's Phone"
main.plugins.bt-tether.mac = ""
main.plugins.bt-tether.phone = "" # android or ios
main.plugins.bt-tether.ip = "" # 192.168.44.2 android / 172.20.10.2 ios
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 = true
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 = true
main.plugins.bt-tether.devices.ios-phone.priority = 999
main.plugins.fix_services.enabled = true
@ -42,8 +59,6 @@ main.plugins.gps.enabled = false
main.plugins.gps.speed = 19200
main.plugins.gps.device = "/dev/ttyUSB0" # for GPSD: "localhost:2947"
main.plugins.gps_listener.enabled = false
main.plugins.grid.enabled = true
main.plugins.grid.report = true
@ -54,16 +69,18 @@ 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.pisugarx.enabled = false
main.plugins.pisugarx.rotation = false
main.plugins.pisugarx.default_display = "percentage"
main.plugins.pisugar3.enabled = false
main.plugins.pisugar3.shutdown = 5
main.plugins.session-stats.enabled = false
main.plugins.session-stats.enabled = true
main.plugins.session-stats.save_directory = "/var/tmp/pwnagotchi/sessions/"
main.plugins.ups_hat_c.enabled = false
@ -87,12 +104,11 @@ 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.plugins.wpa-sec.show_pwd = false
main.iface = "wlan0mon"
main.mon_start_cmd = "/usr/bin/monstart"
main.mon_stop_cmd = "/usr/bin/monstop"
main.mon_max_blind_epochs = 5
main.mon_max_blind_epochs = 50
main.no_restart = false
main.log.path = "/etc/pwnagotchi/log/pwnagotchi.log"
@ -100,6 +116,19 @@ main.log.path-debug = "/etc/pwnagotchi/log/pwnagotchi-debug.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
@ -168,7 +197,7 @@ ui.display.enabled = false
ui.display.rotation = 180
ui.display.type = "waveshare_4"
bettercap.handshakes = "/home/pi/handshakes"
bettercap.handshakes = "/root/handshakes"
bettercap.silence = [
"ble.device.new",
"ble.device.lost",

View File

@ -13,8 +13,7 @@ def is_connected():
try:
# check DNS
host = 'https://api.opwngrid.xyz/api/v1/uptime'
headers = {'user-agent': f'pwnagotchi/{pwnagotchi.__version__}'}
r = requests.get(host, headers=headers, timeout=(30.0, 60.0))
r = requests.get(host, headers=None, timeout=(30.0, 60.0))
if r.json().get('isUp'):
return True
except:
@ -71,9 +70,10 @@ def update_data(last_session):
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': "No AI!",
'ai': ai,
'session': {
'duration': last_session.duration,
'epochs': last_session.epochs,
@ -87,6 +87,7 @@ def update_data(last_session):
'peers': last_session.peers,
},
'uname': subprocess.getoutput("uname -a"),
'brain': brain,
'version': pwnagotchi.__version__,
'build': "Pwnagotchi by Jayofelony",
'plugins': enabled,

View File

@ -50,6 +50,10 @@ class KeyPair(object):
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 ...")
@ -59,9 +63,6 @@ class KeyPair(object):
except:
pass
# no exception, keys loaded correctly.
self._view.on_starting()
return
def sign(self, message):
hasher = SHA256.new(message.encode("ascii"))
signer = PKCS1_PSS.new(self.priv_key, saltLen=16)

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 1.5.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-16 20:46+0100\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"
@ -28,11 +28,11 @@ msgstr "Nuwe dag, nuwe jag, nuwe pwns!"
msgid "Hack the Planet!"
msgstr "Hack die wêreld!"
msgid "No more mister Wi-Fi!!"
msgstr ""
msgid "AI ready."
msgstr "AI gereed."
msgid "Pretty fly 4 a Wi-Fi!"
msgstr ""
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 ..."
@ -257,9 +257,3 @@ msgstr "minuut"
msgid "second"
msgstr "tweede"
#~ msgid "AI ready."
#~ msgstr "AI gereed."
#~ msgid "The neural network is ready."
#~ msgstr "Die neurale netwerk is gereed."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Pwnagotchi Belarusian translation v 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-16 20:46+0100\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"
@ -33,11 +33,11 @@ msgstr "Новы дзень, новае паляванне, новыя ўзло
msgid "Hack the Planet!"
msgstr "Узламай гэту Планету!"
msgid "No more mister Wi-Fi!!"
msgstr ""
msgid "AI ready."
msgstr "A.I. гатовы."
msgid "Pretty fly 4 a Wi-Fi!"
msgstr ""
msgid "The neural network is ready."
msgstr "Нейронная сетка гатова."
msgid "Generating keys, do not turn off ..."
msgstr "Генерацыя ключоў, не выключайце..."
@ -263,12 +263,6 @@ msgstr "хвіліну"
msgid "second"
msgstr ""
#~ msgid "AI ready."
#~ msgstr "A.I. гатовы."
#~ msgid "The neural network is ready."
#~ msgstr "Нейронная сетка гатова."
#, python-brace-format
#~ msgid "Unit {name} is nearby! {name}"
#~ msgstr "Мэта {name} побач!"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-16 20:46+0100\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"
@ -28,11 +28,11 @@ msgstr "Нов ден, нов лов, нови pwns!"
msgid "Hack the Planet!"
msgstr "Хакни планетата!"
msgid "No more mister Wi-Fi!!"
msgstr ""
msgid "AI ready."
msgstr "AI готов."
msgid "Pretty fly 4 a Wi-Fi!"
msgstr ""
msgid "The neural network is ready."
msgstr "Невронната мрежа е готова."
msgid "Generating keys, do not turn off ..."
msgstr "Генериране на ключове, не изключвай ..."
@ -258,9 +258,3 @@ msgstr "минута"
msgid "second"
msgstr "секунда"
#~ msgid "AI ready."
#~ msgstr "AI готов."
#~ msgid "The neural network is ready."
#~ msgstr "Невронната мрежа е готова."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\n"
"POT-Creation-Date: 2023-11-16 21:51+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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
"POT-Creation-Date: 2023-11-16 21:51+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Czechball <czechball@users.noreply.github.com>\n"
"Language-Team: pwnagotchi <33197631+dadav@users.noreply.github.com>\n"
@ -28,11 +28,11 @@ msgstr "Nový den, nový lov, nové úlovky!"
msgid "Hack the Planet!"
msgstr "Hackni celou planetu!"
msgid "No more mister Wi-Fi!!"
msgstr ""
msgid "AI ready."
msgstr "AI připraveno."
msgid "Pretty fly 4 a Wi-Fi!"
msgstr ""
msgid "The neural network is ready."
msgstr "Neuronová síť je připravena."
msgid "Generating keys, do not turn off ..."
msgstr "Generování klíčů, nevypínej mě..."
@ -257,9 +257,3 @@ msgstr "minuta"
msgid "second"
msgstr "sekunda"
#~ msgid "AI ready."
#~ msgstr "AI připraveno."
#~ msgid "The neural network is ready."
#~ msgstr "Neuronová síť je připravena."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
"POT-Creation-Date: 2023-11-16 21:51+0100\n"
"PO-Revision-Date: 2020-01-18 21:56+ZONE\n"
"Last-Translator: Dennis Kjær Jensen <signout@signout.dk>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -28,11 +28,11 @@ msgstr "Ny dag, ny jagt, nye pwns!"
msgid "Hack the Planet!"
msgstr "Hack planeten!"
msgid "No more mister Wi-Fi!!"
msgstr ""
msgid "AI ready."
msgstr "AI klar."
msgid "Pretty fly 4 a Wi-Fi!"
msgstr ""
msgid "The neural network is ready."
msgstr "Det neurale netværk er klart."
msgid "Generating keys, do not turn off ..."
msgstr "Genererer nøgler, sluk ikke ..."
@ -257,9 +257,3 @@ msgstr "minut"
msgid "second"
msgstr "sekund"
#~ msgid "AI ready."
#~ msgstr "AI klar."
#~ msgid "The neural network is ready."
#~ msgstr "Det neurale netværk er klart."

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
"POT-Creation-Date: 2023-11-16 21:51+0100\n"
"PO-Revision-Date: 2019-09-29 14:00+0200\n"
"Last-Translator: dadav <33197631+dadav@users.noreply.github.com>\n"
"Language-Team: DE <33197631+dadav@users.noreply.github.com>\n"
@ -28,11 +28,11 @@ msgstr "Neuer Tag, neue Jagd, neue Pwns!"
msgid "Hack the Planet!"
msgstr "Hack den Planeten!"
msgid "No more mister Wi-Fi!!"
msgstr ""
msgid "AI ready."
msgstr "KI bereit."
msgid "Pretty fly 4 a Wi-Fi!"
msgstr ""
msgid "The neural network is ready."
msgstr "Das neurale Netz ist bereit."
msgid "Generating keys, do not turn off ..."
msgstr "Generiere Schlüssel, nicht ausschalten..."
@ -257,9 +257,3 @@ msgstr "Minute"
msgid "second"
msgstr "Sekunde"
#~ msgid "AI ready."
#~ msgstr "KI bereit."
#~ msgid "The neural network is ready."
#~ msgstr "Das neurale Netz ist bereit."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -3,11 +3,12 @@
# 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: 2024-12-16 20:46+0100\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"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!"
msgstr ""
msgid "No more mister Wi-Fi!!"
msgid "AI ready."
msgstr ""
msgid "Pretty fly 4 a Wi-Fi!"
msgid "The neural network is ready."
msgstr ""
msgid "Generating keys, do not turn off ..."

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-16 20:46+0100\n"
"POT-Creation-Date: 2023-11-16 21:51+0100\n"
"PO-Revision-Date: 2019-10-03 08:00+0000\n"
"Last-Translator: Periklis Fregkos <fregkos@gmail.com>\n"
"Language-Team: pwnagotchi <33197631+dadav@users.noreply.github.com>\n"
@ -29,11 +29,11 @@ msgstr "Νέα μέρα, νέο κυνήγι, νέα pwns!"
msgid "Hack the Planet!"
msgstr "Hackαρε τον πλανήτη!"
msgid "No more mister Wi-Fi!!"
msgstr ""
msgid "AI ready."
msgstr "ΤΝ έτοιμη."
msgid "Pretty fly 4 a Wi-Fi!"
msgstr ""
msgid "The neural network is ready."
msgstr "Το νευρωνικό δίκτυο είναι έτοιμο."
msgid "Generating keys, do not turn off ..."
msgstr ""
@ -258,9 +258,3 @@ msgstr ""
msgid "second"
msgstr ""
#~ msgid "AI ready."
#~ msgstr "ΤΝ έτοιμη."
#~ msgid "The neural network is ready."
#~ msgstr "Το νευρωνικό δίκτυο είναι έτοιμο."

Some files were not shown because too many files have changed in this diff Show More