Compare commits

..

2 Commits

Author SHA1 Message Date
744a50aa67 Update defaults.toml
Signed-off-by: wpa-2 <9049886+wpa-2@users.noreply.github.com>
2024-02-28 19:09:14 +00:00
eb6e229d36 Update defaults.toml
added some bluetooth information 

Signed-off-by: wpa-2 <9049886+wpa-2@users.noreply.github.com>
2024-02-28 17:57:04 +00:00
638 changed files with 9189 additions and 15654 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

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

@ -0,0 +1,72 @@
name: Publish
on:
workflow_dispatch:
inputs:
version:
description: 'Version number'
required: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Extract version from file
id: get_version
run: |
VERSION=$(cut -d "'" -f2 < pwnagotchi/_version.py)
echo "VERSION=$VERSION" >> $GITHUB_ENV
- name: Get latest tag
uses: actions-ecosystem/action-get-latest-tag@v1
id: get-latest-tag
- name: Set LAST_VERSION as an environment variable
run: echo "LAST_VERSION=${{ steps.get-latest-tag.outputs.tag }}" >> $GITHUB_ENV
- name: Generate release notes
id: generate_release_notes
run: |
COMMITS=$(git log --merges --pretty=format:"* %s" $LAST_VERSION--$VERSION | sed 's/$/\\n/g')
CONTRIBUTORS=$(git shortlog -sn $LAST_VERSION--$VERSION | awk '{print "* @" $2}' | sed 's/$/\\n/g')
RELEASE_BODY="**Full Changelog**: https://github.com/jayofelony/pwnagotchi/compare/$LAST_VERSION...$VERSION"
echo "RELEASE_BODY=$RELEASE_BODY" >> $GITHUB_ENV
- name: Install qemu dependencies
run: sudo apt update && sudo apt install qemu-user-static qemu-utils xz-utils -y
- name: Build img file
run: ls -la .; pwd; make all
- name: Transfer 32bit.img to docker and give permissions
run: sudo chown runner:docker "pwnagotchi-32bit.img"
- name: Transfer 64bit.img to docker and give permissions
run: sudo chown runner:docker "pwnagotchi-64bit.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
find /home/runner/work/ -type f -name "*.img" -exec sudo pishrink.sh -aZ {} \;
- name: Change name of 32.img.xz to add version
run: mv "pwnagotchi-32bit.img.xz" "pwnagotchi-$VERSION-32bit.img.xz"
- name: Change name of 64.img.xz to add version
run: mv "pwnagotchi-64bit.img.xz" "pwnagotchi-$VERSION-64bit.img.xz"
- name: Release
uses: softprops/action-gh-release@v1
with:
prerelease: true
tag_name: v${{ env.VERSION }}
name: Pwnagotchi v${{ env.VERSION }}
files: |
pwnagotchi-${{ env.VERSION }}-32bit.img.xz
pwnagotchi-${{ env.VERSION }}-64bit.img.xz
body: ${{ env.RELEASE_BODY }}

1
.gitignore vendored
View File

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

19
.idea/deployment.xml generated
View File

@ -1,6 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="PublishConfigData" filePermissions="493" folderPermissions="493" remoteFilesAllowedToDisappearOnAutoupload="false" confirmBeforeUploading="false"> <component name="PublishConfigData" serverName="pwnagotchi" filePermissions="493" folderPermissions="493" remoteFilesAllowedToDisappearOnAutoupload="false" confirmBeforeUploading="false">
<option name="confirmBeforeUploading" value="false" /> <option name="confirmBeforeUploading" value="false" />
<serverData>
<paths name="pwnagotchi">
<serverdata>
<mappings>
<mapping deploy="/usr/local/lib/python3.11/dist-packages/pwnagotchi" local="$PROJECT_DIR$/pwnagotchi" web="/" />
<mapping deploy="/usr/local/bin" local="$PROJECT_DIR$/bin" />
<mapping local="" />
</mappings>
<excludedPaths>
<excludedPath local="true" path="$PROJECT_DIR$/venv" />
<excludedPath local="true" path="$PROJECT_DIR$/pwnagotchi.egg-info" />
<excludedPath local="true" path="$PROJECT_DIR$/dist" />
<excludedPath local="true" path="$PROJECT_DIR$/builder/packer-builder-arm" />
</excludedPaths>
</serverdata>
</paths>
</serverData>
</component> </component>
</project> </project>

2
.idea/misc.xml generated
View File

@ -3,7 +3,7 @@
<component name="Black"> <component name="Black">
<option name="sdkName" value="Python 3.11 (pwnagotchi)" /> <option name="sdkName" value="Python 3.11 (pwnagotchi)" />
</component> </component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (pwnagotchi)" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10" project-jdk-type="Python SDK" />
<component name="PythonCompatibilityInspectionAdvertiser"> <component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="3" /> <option name="version" value="3" />
</component> </component>

3
.idea/pwnagotchi.iml generated
View File

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

10
.idea/sshConfigs.xml generated
View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SshConfigs">
<configs>
<sshConfig authType="PASSWORD" connectionConfig="{&quot;serverAliveInterval&quot;:300}" id="8b69df7d-cec5-421f-8edf-53ed6233f6b6" port="22" customName="pwnagotchi" nameFormat="CUSTOM" useOpenSSHConfig="true">
<option name="customName" value="pwnagotchi" />
</sshConfig>
</configs>
</component>
</project>

14
.idea/webServers.xml generated
View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WebServers">
<option name="servers">
<webServer id="cf2a1148-a103-4472-a782-7debdc6dabf9" name="pwnagotchi">
<fileTransfer accessType="SFTP" port="22" sshConfigId="8b69df7d-cec5-421f-8edf-53ed6233f6b6" sshConfig="pwnagotchi">
<advancedOptions>
<advancedOptions dataProtectionLevel="Private" passiveMode="true" shareSSLContext="true" />
</advancedOptions>
</fileTransfer>
</webServer>
</option>
</component>
</project>

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 README.md
include LICENSE include LICENSE
recursive-include bin *
recursive-include pwnagotchi *.py recursive-include pwnagotchi *.py
recursive-include pwnagotchi *.yml recursive-include pwnagotchi *.yml
recursive-include pwnagotchi *.* recursive-include pwnagotchi *.*

View File

@ -1,3 +1,33 @@
PACKER_VERSION := 1.10.1
PWN_HOSTNAME := pwnagotchi
PWN_VERSION := $(shell cut -d"'" -f2 < pwnagotchi/_version.py)
MACHINE_TYPE := $(shell uname -m)
ifneq (,$(filter x86_64,$(MACHINE_TYPE)))
GOARCH := amd64
else ifneq (,$(filter i686,$(MACHINE_TYPE)))
GOARCH := 386
else ifneq (,$(filter arm64% aarch64%,$(MACHINE_TYPE)))
GOARCH := arm64
else ifneq (,$(filter arm%,$(MACHINE_TYPE)))
GOARCH := arm
else
GOARCH := amd64
$(warning Unable to detect CPU arch from machine type $(MACHINE_TYPE), assuming $(GOARCH))
endif
# The Ansible part of the build can inadvertently change the active hostname of
# the build machine while updating the permanent hostname of the build image.
# If the unshare command is available, use it to create a separate namespace
# so hostname changes won't affect the build machine.
UNSHARE := $(shell command -v unshare)
ifneq (,$(UNSHARE))
UNSHARE := $(UNSHARE) --uts
endif
# sudo apt-get install qemu-user-static qemu-utils
all: clean packer image
update_langs: update_langs:
@for lang in pwnagotchi/locale/*/; do\ @for lang in pwnagotchi/locale/*/; do\
echo "updating language: $$lang ..."; \ echo "updating language: $$lang ..."; \
@ -9,3 +39,22 @@ compile_langs:
echo "compiling language: $$lang ..."; \ echo "compiling language: $$lang ..."; \
./scripts/language.sh compile $$(basename $$lang); \ ./scripts/language.sh compile $$(basename $$lang); \
done done
packer: clean
curl https://releases.hashicorp.com/packer/$(PACKER_VERSION)/packer_$(PACKER_VERSION)_linux_amd64.zip -o /tmp/packer.zip
unzip /tmp/packer.zip -d /tmp
sudo mv /tmp/packer /usr/bin/packer
image: clean packer
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
bullseye: clean packer
cd builder && sudo /usr/bin/packer init data/32bit/raspberrypi32.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" data/32bit/raspberrypi32.json.pkr.hcl
sudo pishrink -vaZ pwnagotchi-32bit.img
bookworm: clean packer
cd builder && sudo /usr/bin/packer init data/64bit/raspberrypi64.json.pkr.hcl && sudo $(UNSHARE) /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" data/64bit/raspberrypi64.json.pkr.hcl
sudo pishrink -vaZ pwnagotchi-64bit.img
clean:
- rm -rf /tmp/packer*

View File

@ -3,40 +3,37 @@ This is the main source for all forks:
- RPiZeroW (32bit) - RPiZeroW (32bit)
- RPiZero2W, RPi3, RPi4, RPi5 (64bit) - RPiZero2W, RPi3, RPi4, RPi5 (64bit)
**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) [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.ai/) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding 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. full and half WPA handshakes.
![ui](https://i.imgur.com/X68GXrn.png) ![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 ## Documentation
https://github.com/jayofelony/pwnagotchi/wiki https://www.pwnagotchi.ai
https://pwnagotchi.org
## Links ## Links
| &nbsp; | Official Links | | &nbsp; | Official Links |
|-----------|----------------------------------------------------------| |-----------|-------------------------------------------------------------|
| Website | [pwnagotchi.org](https://pwnagotchi.org/) | | Website | [pwnagotchi.ai](https://pwnagotchi.ai/) |
| Chat | [discord](https://discord.gg/PGgnzFbz4M) | | Forum | [community.pwnagotchi.ai](https://community.pwnagotchi.ai/) |
| Subreddit | [r/pwnagotchi](https://www.reddit.com/r/pwnagotchi/) | | Slack | [pwnagotchi.slack.com](https://invite.pwnagotchi.ai/) |
| Subreddit | [r/pwnagotchi](https://www.reddit.com/r/pwnagotchi/) |
| Twitter | [@pwnagotchi](https://twitter.com/pwnagotchi) |
## License ## License
`pwnagotchi` created by [@evilsocket](https://twitter.com/evilsocket) and updated by [us](https://github.com/jayofelony/pwnagotchi/graphs/contributors). It is released under the GPL3 license. `pwnagotchi` is made with ♥ by [@evilsocket](https://twitter.com/evilsocket) and the [amazing dev team](https://github.com/evilsocket/pwnagotchi/graphs/contributors). It is released under the GPL3 license.

229
bin/pwnagotchi Executable file
View File

@ -0,0 +1,229 @@
#!/usr/bin/python3
import logging
import argparse
import time
import signal
import sys
import toml
import requests
import os
import pwnagotchi
from pwnagotchi import utils
from pwnagotchi.google import cmd as google_cmd
from pwnagotchi.plugins import cmd as plugins_cmd
from pwnagotchi import log
from pwnagotchi import restart
from pwnagotchi import fs
from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple
def do_clear(display):
logging.info("clearing the display ...")
display.clear()
sys.exit(0)
def do_manual_mode(agent):
logging.info("entering manual mode ...")
agent.mode = 'manual'
agent.last_session.parse(agent.view(), args.skip_session)
if not args.skip_session:
logging.info(
"the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % (
agent.last_session.duration_human,
agent.last_session.epochs,
agent.last_session.train_epochs,
agent.last_session.avg_reward,
agent.last_session.min_reward,
agent.last_session.max_reward))
while True:
display.on_manual_mode(agent.last_session)
time.sleep(5)
if grid.is_connected():
plugins.on('internet_available', agent)
def do_auto_mode(agent):
logging.info("entering auto mode ...")
agent.mode = 'auto'
agent.start()
while True:
try:
# recon on all channels
agent.recon()
# get nearby access points grouped by channel
channels = agent.get_access_points_by_channel()
# for each channel
for ch, aps in channels:
agent.set_channel(ch)
if not agent.is_stale() and agent.any_activity():
logging.info("%d access points on channel %d" % (len(aps), ch))
# for each ap on this channel
for ap in aps:
# send an association frame in order to get for a PMKID
agent.associate(ap)
# deauth all client stations in order to get a full handshake
for sta in ap['clients']:
agent.deauth(ap, sta)
time.sleep(1) # delay to not trigger nexmon firmware bugs
# An interesting effect of this:
#
# From Pwnagotchi's perspective, the more new access points
# and / or client stations nearby, the longer one epoch of
# its relative time will take ... basically, in Pwnagotchi's universe,
# Wi-Fi electromagnetic fields affect time like gravitational fields
# affect ours ... neat ^_^
agent.next_epoch()
if grid.is_connected():
plugins.on('internet_available', agent)
except Exception as e:
if str(e).find("wifi.interface not set") > 0:
logging.exception("main loop exception due to unavailable wifi device, likely programmatically disabled (%s)", e)
logging.info("sleeping 60 seconds then advancing to next epoch to allow for cleanup code to trigger")
time.sleep(60)
agent.next_epoch()
else:
logging.exception("main loop exception (%s)", e)
if __name__ == '__main__':
def add_parsers(parser):
"""
Adds the plugins and google subcommands
"""
subparsers = parser.add_subparsers()
# Add parsers from plugins_cmd
plugins_cmd.add_parsers(subparsers)
# Add parsers from google_cmd
google_cmd.add_parsers(subparsers)
parser = argparse.ArgumentParser(prog="pwnagotchi")
# pwnagotchi --help
parser.add_argument('-C', '--config', action='store', dest='config', default='/etc/pwnagotchi/default.toml',
help='Main configuration file.')
parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/etc/pwnagotchi/config.toml',
help='If this file exists, configuration will be merged and this will override default values.')
parser.add_argument('--manual', dest="do_manual", action="store_true", default=False, help="Manual mode.")
parser.add_argument('--skip-session', dest="skip_session", action="store_true", default=False,
help="Skip last session parsing in manual mode.")
parser.add_argument('--clear', dest="do_clear", action="store_true", default=False,
help="Clear the ePaper display and exit.")
parser.add_argument('--debug', dest="debug", action="store_true", default=False,
help="Enable debug logs.")
parser.add_argument('--version', dest="version", action="store_true", default=False,
help="Print the version.")
parser.add_argument('--print-config', dest="print_config", action="store_true", default=False,
help="Print the configuration.")
# Jayofelony added these
parser.add_argument('--check-update', dest="check_update", action="store_true", default=False,
help="Check for updates on Pwnagotchi. And tells current version.")
parser.add_argument('--donate', dest="donate", action="store_true", default=False,
help="How to donate to this project.")
# pwnagotchi plugins --help
add_parsers(parser)
args = parser.parse_args()
if plugins_cmd.used_plugin_cmd(args):
config = utils.load_config(args)
log.setup_logging(args, config)
rc = plugins_cmd.handle_cmd(args, config)
sys.exit(rc)
if google_cmd.used_google_cmd(args):
config = utils.load_config(args)
log.setup_logging(args, config)
rc = google_cmd.handle_cmd(args)
sys.exit(rc)
if args.version:
print(pwnagotchi.__version__)
sys.exit(0)
if args.donate:
print("Donations can made @ \n "
"https://www.patreon.com/pwnagotchi_torch \n "
"https://github.com/sponsors/jayofelony \n\n"
"But only if you really want to!")
sys.exit(0)
if args.check_update:
resp = requests.get("https://api.github.com/repos/jayofelony/pwnagotchi/releases/latest")
latest = resp.json()
latest_ver = latest['tag_name'].replace('v', '')
local = version_to_tuple(pwnagotchi.__version__)
remote = version_to_tuple(latest_ver)
if remote > local:
user_input = input("There is a new version available! Update from v%s to v%s?\n[y(es)/n(o)]"
% (pwnagotchi.__version__, latest_ver))
# input validation
if user_input.lower() in ('y', 'yes'):
if os.path.exists('/root/.auto-update'):
os.system("rm /root/.auto-update && systemctl restart pwnagotchi")
else:
logging.error("You should make sure auto-update is enabled!")
print("Okay, give me a couple minutes. Just watch pwnlog while you wait.")
elif user_input.lower() in ('n', 'no'): # using this elif for readability
print("Okay, guess not!")
else:
print("You are currently on the latest release, v%s." % pwnagotchi.__version__)
sys.exit(0)
config = utils.load_config(args)
if args.print_config:
print(toml.dumps(config, encoder=DottedTomlEncoder()))
sys.exit(0)
from pwnagotchi.identity import KeyPair
from pwnagotchi.agent import Agent
from pwnagotchi.ui import fonts
from pwnagotchi.ui.display import Display
from pwnagotchi import grid
from pwnagotchi import plugins
pwnagotchi.config = config
fs.setup_mounts(config)
log.setup_logging(args, config)
fonts.init(config)
pwnagotchi.set_name(config['main']['name'])
plugins.load(config)
display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()})
if args.do_clear:
do_clear(display)
sys.exit(0)
agent = Agent(view=display, config=config, keypair=KeyPair(view=display))
def usr1_handler(*unused):
logging.info('Received USR1 singal. Restart process ...')
restart("MANU" if args.do_manual else "AUTO")
signal.signal(signal.SIGUSR1, usr1_handler)
if args.do_manual:
do_manual_mode(agent)
else:
do_auto_mode(agent)

View File

@ -0,0 +1,173 @@
packer {
required_plugins {
arm = {
version = "1.0.0"
source = "github.com/cdecoux/builder-arm"
}
ansible = {
source = "github.com/hashicorp/ansible"
version = ">= 1.1.1"
}
}
}
variable "pwn_hostname" {
type = string
}
variable "pwn_version" {
type = string
}
source "arm" "rpi64-pwnagotchi" {
file_checksum_url = "https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-12-11/2023-12-11-raspios-bookworm-arm64-lite.img.xz.sha256"
file_urls = ["https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-12-11/2023-12-11-raspios-bookworm-arm64-lite.img.xz"]
file_checksum_type = "sha256"
file_target_extension = "xz"
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
image_path = "../pwnagotchi-64bit.img"
qemu_binary_source_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
image_build_method = "resize"
image_size = "9G"
image_type = "dos"
image_partitions {
name = "boot"
type = "c"
start_sector = "8192"
filesystem = "fat"
size = "256M"
mountpoint = "/boot/firmware"
}
image_partitions {
name = "root"
type = "83"
start_sector = "532480"
filesystem = "ext4"
size = "0"
mountpoint = "/"
}
}
source "arm" "rpi32-pwnagotchi" {
file_checksum_url = "https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2023-12-06/2023-12-05-raspios-bullseye-armhf-lite.img.xz.sha256"
file_urls = ["https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2023-12-06/2023-12-05-raspios-bullseye-armhf-lite.img.xz"]
file_checksum_type = "sha256"
file_target_extension = "xz"
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
image_path = "../pwnagotchi-32bit.img"
qemu_binary_source_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P"
qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P"
image_build_method = "resize"
image_size = "9G"
image_type = "dos"
image_partitions {
name = "boot"
type = "c"
start_sector = "8192"
filesystem = "fat"
size = "256M"
mountpoint = "/boot"
}
image_partitions {
name = "root"
type = "83"
start_sector = "532480"
filesystem = "ext4"
size = "0"
mountpoint = "/"
}
}
# a build block invokes sources and runs provisioning steps on them. The
# documentation for build blocks can be found here:
# https://www.packer.io/docs/from-1.5/blocks/build
build {
name = "Raspberry Pi 64 Pwnagotchi"
sources = ["source.arm.rpi64-pwnagotchi"]
provisioner "file" {
destination = "/usr/bin/"
sources = [
"data/64bit/usr/bin/bettercap-launcher",
"data/64bit/usr/bin/hdmioff",
"data/64bit/usr/bin/hdmion",
"data/64bit/usr/bin/monstart",
"data/64bit/usr/bin/monstop",
"data/64bit/usr/bin/pwnagotchi-launcher",
"data/64bit/usr/bin/pwnlib",
]
}
provisioner "shell" {
inline = ["chmod +x /usr/bin/*"]
}
provisioner "file" {
destination = "/etc/systemd/system/"
sources = [
"data/64bit/etc/systemd/system/bettercap.service",
"data/64bit/etc/systemd/system/pwnagotchi.service",
"data/64bit/etc/systemd/system/pwngrid-peer.service",
]
}
provisioner "file" {
destination = "/etc/update-motd.d/01-motd"
source = "data/64bit/etc/update-motd.d/01-motd"
}
provisioner "shell" {
inline = ["chmod +x /etc/update-motd.d/*"]
}
provisioner "shell" {
inline = ["apt-get -y --allow-releaseinfo-change update", "apt-get -y dist-upgrade", "apt-get install -y --no-install-recommends ansible"]
}
provisioner "ansible-local" {
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
playbook_file = "data/64bit/raspberrypi64.yml"
}
}
build {
name = "Raspberry Pi 32 Pwnagotchi"
sources = ["source.arm.rpi32-pwnagotchi"]
provisioner "file" {
destination = "/usr/bin/"
sources = [
"data/32bit/usr/bin/bettercap-launcher",
"data/32bit/usr/bin/hdmioff",
"data/32bit/usr/bin/hdmion",
"data/32bit/usr/bin/monstart",
"data/32bit/usr/bin/monstop",
"data/32bit/usr/bin/pwnagotchi-launcher",
"data/32bit/usr/bin/pwnlib",
]
}
provisioner "shell" {
inline = ["chmod +x /usr/bin/*"]
}
provisioner "file" {
destination = "/etc/systemd/system/"
sources = [
"data/32bit/etc/systemd/system/bettercap.service",
"data/32bit/etc/systemd/system/pwnagotchi.service",
"data/32bit/etc/systemd/system/pwngrid-peer.service",
]
}
provisioner "file" {
destination = "/etc/update-motd.d/01-motd"
source = "data/32bit/etc/update-motd.d/01-motd"
}
provisioner "shell" {
inline = ["chmod +x /etc/update-motd.d/*"]
}
provisioner "shell" {
inline = ["apt-get -y --allow-releaseinfo-change update", "apt-get -y dist-upgrade", "apt-get install -y --no-install-recommends ansible"]
}
provisioner "ansible-local" {
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
playbook_dir = "data/32bit/extras/"
playbook_file = "data/32bit/raspberrypi32.yml"
}
}

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 --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,62 @@
# A sample configuration for dhcpcd.
# See dhcpcd.conf(5) for details.
# Allow users of this group to interact with dhcpcd via the control socket.
#controlgroup wheel
# Inform the DHCP server of our hostname for DDNS.
hostname
# Use the hardware address of the interface for the Client ID.
clientid
# or
# Use the same DUID + IAID as set in DHCPv6 for DHCPv4 ClientID as per RFC4361.
# Some non-RFC compliant DHCP servers do not reply with this set.
# In this case, comment out duid and enable clientid above.
#duid
# Persist interface configuration when dhcpcd exits.
persistent
# Rapid commit support.
# Safe to enable by default because it requires the equivalent option set
# on the server to actually work.
option rapid_commit
# A list of options to request from the DHCP server.
option domain_name_servers, domain_name, domain_search, host_name
option classless_static_routes
# Respect the network MTU. This is applied to DHCP routes.
option interface_mtu
# Most distributions have NTP support.
#option ntp_servers
# A ServerID is required by RFC2131.
require dhcp_server_identifier
# Generate SLAAC address using the Hardware Address of the interface
#slaac hwaddr
# OR generate Stable Private IPv6 Addresses based from the DUID
slaac private
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# !! DO NOT EDIT THESE LINES BELOW PLEASE !!
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# static IP configuration:
denyinterfaces wlan0
interface eth0
static domain_name_servers=8.8.8.8 1.1.1.1
metric 201
interface usb0
static ip_address=10.0.0.2/24
static routers=10.0.0.1
static domain_name_servers=10.0.0.1 8.8.8.8 1.1.1.1
metric 202
interface bnep0
static domain_name_servers=8.8.8.8 1.1.1.1
metric 203

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,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,6 @@
[Unit]
After=hciuart.service bluetooth.service
Before=
[Service]
ExecStartPre=/bin/sleep 5

View File

@ -0,0 +1,19 @@
[Unit]
Description=pwnagotchi Deep Reinforcement Learning instrumenting bettercap for WiFI pwning.
Documentation=https://pwnagotchi.ai
Wants=network.target
After=pwngrid-peer.service
[Service]
Type=simple
WorkingDirectory=~
ExecStart=/usr/bin/pwnagotchi-launcher
Restart=always
RestartSec=30
TasksMax=infinity
LimitNPROC=infinity
StandardOutput=null
StandardError=null
[Install]
WantedBy=multi-user.target

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,33 @@
#!/bin/sh
_hostname=$(hostname)
_version=$(cut -d"'" -f2 < /usr/local/lib/python3.9/dist-packages/pwnagotchi/_version.py)
echo
echo "(◕‿‿◕) $_hostname"
echo
echo " Hi! I'm a pwnagotchi $_version, please take good care of me!"
echo " Here are some basic things you need to know to raise me properly!"
echo
echo " If you want to change my configuration, use /etc/pwnagotchi/config.toml"
echo " All plugin config files are located in /etc/pwnagotchi/conf.d/"
echo " Read the readme if you want to use gdrivesync plugin!!"
echo
echo " All the configuration options can be found on /etc/pwnagotchi/default.toml,"
echo " but don't change this file because I will recreate it every time I'm restarted!"
echo
echo " I use oPwnGrid as my main API, you can check stats at https://opwngrid.xyz"
echo
echo " I'm managed by systemd. Here are some basic commands."
echo
echo " If you want to know what I'm doing, you can check my logs with the command"
echo " - pwnlog"
echo " - sudo pwnagotchi --version, to check the current version"
echo " - sudo pwnagotchi --donate, to see how you can donate to this project"
echo " - sudo pwnagotchi --check-update, to see if there is a new version available"
echo
echo " If you want to know if I'm running, you can use"
echo " sudo systemctl status pwnagotchi"
echo
echo " You can restart me using"
echo " pwnkill"
echo
echo " You learn more about me at https://pwnagotchi.ai/"

View File

@ -0,0 +1,40 @@
# Install nexmon to fix wireless scanning (takes 2.5G of space)
- name: clone nexmon repository
git:
repo: https://github.com/DrSchottky/nexmon.git
dest: /usr/local/src/nexmon
- name: make firmware
shell: "source ./setup_env.sh && make"
args:
executable: /bin/bash
chdir: /usr/local/src/nexmon/
- name: "make firmware patch ({{ item.name }})"
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/{{ item.patch }}/nexmon/ && make"
args:
executable: /bin/bash
chdir: /usr/local/src/nexmon/
environment:
QEMU_UNAME: "{{ item.kernel }}"
ARCHFLAGS: "{{ item.arch_flags }}"
- name: "install new firmware ({{ item.name }})"
copy:
src: "/usr/local/src/nexmon/patches/{{ item.patch }}/nexmon/{{ item.firmware }}"
dest: "/usr/lib/firmware/brcm/{{ item.firmware }}"
follow: true
environment:
QEMU_UNAME: "{{ item.kernel }}"
ARCHFLAGS: "{{ item.arch_flags }}"
- name: backup original driver
command: "mv /usr/lib/modules/{{ item.kernel }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz /usr/lib/modules/{{ item.kernel }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz.orig"
- name: copy modified driver
copy:
src: "/usr/local/src/nexmon/patches/driver/brcmfmac_6.1.y-nexmon/brcmfmac.ko"
dest: "/usr/lib/modules/{{ item.kernel }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko"
- name : load brcmfmac drivers
command: "/sbin/depmod -a {{ item.kernel }}"

View File

@ -0,0 +1,94 @@
packer {
required_plugins {
arm = {
version = "1.0.0"
source = "github.com/cdecoux/builder-arm"
}
ansible = {
source = "github.com/hashicorp/ansible"
version = ">= 1.1.1"
}
}
}
variable "pwn_hostname" {
type = string
}
variable "pwn_version" {
type = string
}
source "arm" "rpi32-pwnagotchi" {
file_checksum_url = "https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2023-12-06/2023-12-05-raspios-bullseye-armhf-lite.img.xz.sha256"
file_urls = ["https://downloads.raspberrypi.com/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2023-12-06/2023-12-05-raspios-bullseye-armhf-lite.img.xz"]
file_checksum_type = "sha256"
file_target_extension = "xz"
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
image_path = "../../pwnagotchi-32bit.img"
qemu_binary_source_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P"
qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/arm-binfmt-P"
image_build_method = "resize"
image_size = "9G"
image_type = "dos"
image_partitions {
name = "boot"
type = "c"
start_sector = "8192"
filesystem = "fat"
size = "256M"
mountpoint = "/boot"
}
image_partitions {
name = "root"
type = "83"
start_sector = "532480"
filesystem = "ext4"
size = "0"
mountpoint = "/"
}
}
build {
name = "Raspberry Pi 32 Pwnagotchi"
sources = ["source.arm.rpi32-pwnagotchi"]
provisioner "file" {
destination = "/usr/bin/"
sources = [
"data/32bit/usr/bin/bettercap-launcher",
"data/32bit/usr/bin/hdmioff",
"data/32bit/usr/bin/hdmion",
"data/32bit/usr/bin/monstart",
"data/32bit/usr/bin/monstop",
"data/32bit/usr/bin/pwnagotchi-launcher",
"data/32bit/usr/bin/pwnlib",
]
}
provisioner "shell" {
inline = ["chmod +x /usr/bin/*"]
}
provisioner "file" {
destination = "/etc/systemd/system/"
sources = [
"data/32bit/etc/systemd/system/bettercap.service",
"data/32bit/etc/systemd/system/pwnagotchi.service",
"data/32bit/etc/systemd/system/pwngrid-peer.service",
]
}
provisioner "file" {
destination = "/etc/update-motd.d/01-motd"
source = "data/32bit/etc/update-motd.d/01-motd"
}
provisioner "shell" {
inline = ["chmod +x /etc/update-motd.d/*"]
}
provisioner "shell" {
inline = ["apt-get -y --allow-releaseinfo-change update", "apt-get -y dist-upgrade", "apt-get install -y --no-install-recommends ansible"]
}
provisioner "ansible-local" {
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
playbook_dir = "data/32bit/extras/"
playbook_file = "data/32bit/raspberrypi32.yml"
}
}

View File

@ -0,0 +1,606 @@
---
- hosts:
- 127.0.0.1
gather_facts: true
become: true
vars:
boards:
- {
kernel: "6.1.21+",
name: "PiZeroW",
firmware: "brcmfmac43430-sdio.bin",
patch: "bcm43430a1/7_45_41_46",
cpu: arm1176,
arch_flags: "-arch armv6l"
}
- {
kernel: "6.1.21-v7+",
name: "PiZero2W",
firmware: "brcmfmac43436-sdio.bin",
patch: "bcm43436b0/9_88_4_65",
cpu: any, #cortex-a53
arch_flags: "-arch armv7l"
}
- {
kernel: "6.1.21-v7l+",
name: "Pi4b_32",
firmware: "brcmfmac43455-sdio.bin",
patch: "bcm43455c0/7_45_206",
cpu: any, #cortex-a72
arch_flags: "-arch armv7l"
}
kernel:
min: "6.1"
full: "6.1.21+"
full_2w: "6.1.21-v7+"
full_4b: "6.1.21-v7l+"
arch: "v6l"
pwnagotchi:
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi-torch', true) }}"
custom_plugin_dir: "/usr/local/share/pwnagotchi/custom-plugins"
system:
boot_options:
- "#### pwnagotchi additions"
- "# this pwnagotchi image is 32-bit only no v8+ headers to build nexmon for 64 bit"
- "arm_64bit=0"
- "# dwc2 for RNDIS. comment out, and remove dwc2 and g_ether from cmdline.txt for X306 usb battery hat"
- "dtoverlay=dwc2"
- "dtoverlay=spi1-3cs"
- "dtparam=i2c1=on"
- "dtparam=i2c_arm=on"
- "dtparam=spi=on"
- "gpu_mem=16"
- "#### audio out on pins 18 and 19"
- "#dtoverlay=audremap,pins_18_19"
- "#### touchscreen on waveshare touch e-paper"
- "#dtoverlay=goodix,interrupt=27,reset=22"
- "#### for PWM backlighting on pimoroni displayhatmini"
- "dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4"
modules:
- "i2c-dev"
services:
enable:
- bettercap.service
- bluetooth.service
- dphys-swapfile.service
- fstrim.timer
- pwnagotchi.service
- pwngrid-peer.service
disable:
- apt-daily-upgrade.service
- apt-daily-upgrade.timer
- apt-daily.service
- apt-daily.timer
- ifup@wlan0.service
- triggerhappy.service
- wpa_supplicant.service
packages:
caplets:
source: "https://github.com/jayofelony/caplets.git"
bettercap:
source: "https://github.com/jayofelony/bettercap.git"
url: "https://github.com/jayofelony/bettercap/releases/download/2.32.2/bettercap-2.32.2-armhf.zip"
ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
opwngrid:
source: "https://github.com/jayofelony/pwngrid.git"
url: "https://github.com/jayofelony/pwngrid/releases/download/v1.10.7/pwngrid-1.10.7-armhf.zip"
torch:
wheel: "torch-2.1.0a0+gitunknown-cp39-cp39-linux_armv6l.whl"
url: "https://github.com/Sniffleupagus/Torch4Pizero/releases/download/v1.0.0/torch-2.1.0a0+gitunknown-cp39-cp39-linux_armv6l.whl"
torchvision:
wheel: "torchvision-0.16.0a0-cp39-cp39-linux_armv6l.whl"
url: "https://github.com/Sniffleupagus/Torch4Pizero/releases/download/v1.0.0/torchvision-0.16.0a0-cp39-cp39-linux_armv6l.whl"
apt:
downgrade:
- libpcap-dev_1.9.1-4_armhf.deb
- libpcap0.8-dbg_1.9.1-4_armhf.deb
- libpcap0.8-dev_1.9.1-4_armhf.deb
- libpcap0.8_1.9.1-4_armhf.deb
hold:
- firmware-atheros
- firmware-brcm80211
- firmware-libertas
- firmware-misc-nonfree
- firmware-realtek
- libpcap-dev
- libpcap0.8
- libpcap0.8-dev
- libpcap0.8-dbg
remove:
- avahi-daemon
- nfs-common
- triggerhappy
- wpasupplicant
install:
- autoconf
- bc
- bison
- bluez
- build-essential
- curl
- dkms
- dphys-swapfile
- espeak-ng
- evtest
- fbi
- flex
- fonts-dejavu
- fonts-dejavu-core
- fonts-dejavu-extra
- fonts-freefont-ttf
- g++
- gawk
- gcc-arm-none-eabi
- git
- libatlas-base-dev
- libavcodec58
- libavformat58
- libblas-dev
- libbluetooth-dev
- libbz2-dev
- libc-ares-dev
- libc6-dev
- libcpuinfo-dev
- libdbus-1-dev
- libdbus-glib-1-dev
- libeigen3-dev
- libelf-dev
- libffi-dev
- libfl-dev
- libfuse-dev
- libgdbm-dev
- libgl1-mesa-glx
- libgmp3-dev
- libgstreamer1.0-0
- libhdf5-dev
- liblapack-dev
- libncursesw5-dev
- libnetfilter-queue-dev
- libopenblas-dev
- libopenjp2-7
- libopenmpi-dev
- libopenmpi3
- libpcap-dev
- libprotobuf-dev
- libraspberrypi-bin
- libraspberrypi-dev
- libraspberrypi-doc
- libraspberrypi0
- libsleef-dev
- libsqlite3-dev
- libssl-dev
- libswscale5
- libtiff5
- libtool
- libts-bin
- libusb-1.0-0-dev
- lsof
- make
- python3-flask
- python3-flask-cors
- python3-flaskext.wtf
- python3-pil
- python3-pip
- python3-protobuf
- python3-smbus
- qpdf
- raspberrypi-kernel-headers
- rsync
- screen
- tcpdump
- texinfo
- time
- tk-dev
- unzip
- vim
- wget
- wl
- xxd
- zlib1g-dev
tasks:
- name: Create pi user
copy:
dest: /boot/userconf
content: |
pi:$6$3jNr0GA9KIyt4hmM$efeVIopdMQ8DGgEPCWWlbx3mJJNAYci1lEXGdlky0xPyjqwKNbwTL5SrCcpb4144C4IvzWjn7Iv.QjqmU7iyT/
- name: change hostname
lineinfile:
dest: /etc/hostname
regexp: '^raspberrypi'
line: "{{pwnagotchi.hostname}}"
state: present
when: lookup('file', '/etc/hostname') == "raspberrypi"
register: hostname
- name: add hostname to /etc/hosts
lineinfile:
dest: /etc/hosts
regexp: '^127\.0\.1\.1[ \t]+raspberrypi'
line: "127.0.1.1\t{{pwnagotchi.hostname}}"
state: present
when: hostname.changed
- name: Create custom plugin directory
file:
path: '{{ pwnagotchi.custom_plugin_dir }}'
state: directory
- name: update apt package cache
apt:
update_cache: yes
- name: install packages
apt:
name: "{{ packages.apt.install }}"
state: present
- name: update pip3, setuptools, wheel
shell: "python3 -m pip install --upgrade pip setuptools wheel"
args:
executable: /bin/bash
chdir: /usr/local/src
###########################################
#
# libpcap v1.9 - build from source
#
###########################################
# check for presence, then it can re-run in later parts if needed
# use the "make" built in
# install libpcap before bettercap and pwngrid, so they use it
- name: clone libpcap v1.9 from github
git:
repo: 'https://github.com/the-tcpdump-group/libpcap.git'
dest: /usr/local/src/libpcap
version: libpcap-1.9
- name: build and install libpcap into /usr/local/lib
shell: "./configure && make && make install"
args:
executable: /bin/bash
chdir: /usr/local/src/libpcap
- name: remove libpcap build folder
file:
state: absent
path: /usr/local/src/libpcap
- name: create symlink /usr/local/lib/libpcap.so.1.9.1
file:
src: /usr/local/lib/libpcap.so.1.9.1
dest: /usr/local/lib/libpcap.so.0.8
state: link
###############################################################
# Install nexmon to fix wireless scanning (takes 2.5G of space)
###############################################################
# Install nexmon for all boards
- name: build and install nexmon as needed
include_tasks: nexmon.yml
loop: "{{ boards }}"
# some pizero2w have the pizeroW wifi chip
# could this be a link instead of a copy? and force, only if not a link?
- name: copy 43430-sdio as 43436s-sdio for the special 43430/1 /2
copy:
src: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin
dest: /usr/lib/firmware/brcm/brcmfmac43436s-sdio.bin
follow: true
# delete blob files that make nexmon sad
- name: Delete the firmware blob files to avoid some nexmon crashing
file:
state: absent
path: '{{ item }}'
loop:
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.clm_blob
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,model-zero-w.clm_blob
- /usr/lib/firmware/brcm/brcmfmac43430b0-sdio.raspberrypi,model-zero-2-w.clm_blob
- /usr/lib/firmware/brcm/brcmfmac43436-sdio.raspberrypi,model-zero-2-w.clm_blob
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,3-model-b.clm_blob
# To shrink the final image, remove the nexmon directory (takes 2.5G of space) post build and installation
- name: Delete nexmon content & directory
file:
state: absent
path: /usr/local/src/nexmon/
- name: clone pwnagotchi repository
git:
repo: https://github.com/jayofelony/pwnagotchi.git
dest: /usr/local/src/pwnagotchi
register: pwnagotchigit
# is this even necessary? Can't we just link from /home/pi/pwnagotchi to /usr/local/{bin,lib,etc}
# then just git update in the home dir and encourage hacking?
# make owned by pi.pi, and custom plugins.
- name: build pwnagotchi wheel
command: "python3 setup.py sdist bdist_wheel"
args:
chdir: /usr/local/src/pwnagotchi
when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi_version)
- name: download torch whl
get_url:
url: "{{ packages.torch.url }}"
dest: /usr/local/src/
- name: download torchvision whl
get_url:
url: "{{ packages.torchvision.url }}"
dest: /usr/local/src/
- name: install 32-bit pwnagotchi wheel and dependencies with 32-bit torch wheels
pip:
name:
- "{{ lookup('fileglob', '/usr/local/src/pwnagotchi/dist/pwnagotchi*.whl') }}"
- "{{ packages.torch.url }}"
- "{{ packages.torchvision.url }}"
extra_args: "--no-cache-dir"
environment:
QEMU_CPU: arm1176
QEMU_UNAME: "{{ kernel.full }}"
when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi_version)
- name: create /usr/local/share/pwnagotchi/ folder
file:
path: /usr/local/share/pwnagotchi/
state: directory
- name: remove pwnagotchi folder
file:
state: absent
path: /usr/local/src/pwnagotchi
- name: remove torch whl
file:
state: absent
path: "{{ lookup('fileglob', '/usr/local/src/torch*.whl') }}"
##########################################
#
# pwngrid, bettercap
#
##########################################
- name: Install go-1.21
unarchive:
src: https://go.dev/dl/go1.21.6.linux-armv6l.tar.gz
dest: /usr/local
remote_src: yes
register: golang
- name: Update .bashrc for go-1.21
blockinfile:
dest: /home/pi/.bashrc
state: present
block: |
export GOPATH=$HOME/go
export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin
when: golang.changed
- name: download pwngrid
unarchive:
remote_src: yes
src: "{{ packages.opwngrid.url }}"
dest: /usr/local/bin/
mode: 0755
- name: download and install bettercap
unarchive:
src: "{{ packages.bettercap.url }}"
dest: /usr/local/bin
remote_src: yes
exclude:
- README.md
- LICENSE.md
mode: 0755
- name: clone bettercap caplets
git:
repo: "{{ packages.caplets.source }}"
dest: /tmp/caplets
register: capletsgit
- name: install bettercap caplets
make:
chdir: /tmp/caplets
target: install
when: capletsgit.changed
- name: download and install bettercap ui
unarchive:
src: "{{ packages.bettercap.ui }}"
dest: /usr/local/share/bettercap/
remote_src: yes
mode: 0755
# to always have the bettercap webui available (because why not?)
- name: copy pwnagotchi-manual over pwnagotchi-auto caplet
ansible.builtin.copy:
src: /usr/local/share/bettercap/caplets/pwnagotchi-manual.cap
dest: /usr/local/share/bettercap/caplets/pwnagotchi-auto.cap
force: true
ignore_errors: true
- name: create /etc/pwnagotchi folder
file:
path: /etc/pwnagotchi
state: directory
- name: create log folder
file:
path: /home/pi/logs
state: directory
- name: check if user configuration exists
stat:
path: /etc/pwnagotchi/config.toml
register: user_config
- name: create /etc/pwnagotchi/config.toml
copy:
dest: /etc/pwnagotchi/config.toml
content: |
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
# Example:
# ui.display.enabled = true
# ui.display.type = "waveshare_2"
when: not user_config.stat.exists
- name: Delete motd 10-uname
file:
state: absent
path: /etc/update-motd.d/10-uname
- name: enable ssh on boot
file:
path: /boot/ssh
state: touch
- name: adjust /boot/config.txt
lineinfile:
dest: /boot/config.txt
insertafter: EOF
line: '{{ item }}'
with_items: "{{system.boot_options}}"
- name: adjust /etc/modules
lineinfile:
dest: /etc/modules
insertafter: EOF
line: '{{ item }}'
with_items: "{{system.modules}}"
- name: change root partition
replace:
dest: /boot/cmdline.txt
backup: no
regexp: "root=PARTUUID=[a-zA-Z0-9\\-]+"
replace: "root=/dev/mmcblk0p2"
- name: configure /boot/cmdline.txt
lineinfile:
path: /boot/cmdline.txt
backrefs: True
state: present
backup: no
regexp: '(.*)$'
line: '\1 modules-load=dwc2,g_ether'
- name: Add pwnlog alias
lineinfile:
dest: /home/pi/.bashrc
line: "\nalias pwnlog='tail -f -n300 /etc/pwnagotchi/log/pwn*.log | sed --unbuffered \"s/,[[:digit:]]\\{3\\}\\]//g\" | cut -d \" \" -f 2-'"
insertafter: EOF
- name: Add pwnver alias
lineinfile:
dest: /home/pi/.bashrc
line: "\nalias pwnver='python3 -c \"import pwnagotchi as p; print(p.__version__)\"'"
insertafter: EOF
- name: Add pwnkill alias to restart pwnagotchi with a signal
lineinfile:
dest: /home/pi/.bashrc
line: "\nalias pwnkill='sudo killall -USR1 pwnagotchi'"
insertafter: EOF
- name: add firmware packages to hold
dpkg_selections:
name: "{{ item }}"
selection: hold
with_items: "{{ packages.apt.hold }}"
- name: disable unnecessary services
systemd:
name: "{{ item }}"
state: stopped
enabled: no
with_items: "{{ services.disable }}"
- name: enable services
systemd:
name: "{{ item }}"
enabled: true
state: stopped
with_items: "{{ services.enable }}"
#- name: remove golang build libraries
# file:
# state: absent
# path: /root/go
#- name: remove golang
# file:
# state: absent
# path: /usr/local/go
- name: make /root readable, becauase that's where all the files are
file:
path: /root
mode: '755'
- name: fix permissions on /home/pi
file:
path: /home/pi
owner: pi
group: pi
recurse: true
- name: remove unnecessary apt packages
apt:
name: "{{ packages.apt.remove }}"
state: absent
purge: yes
- name: remove dependencies that are no longer required
apt:
autoremove: yes
- name: clean apt cache
apt:
autoclean: true
- name: remove golang build libraries
file:
state: absent
path: /root/go
- name: remove pre-collected packages zip
file:
path: /root/go_pkgs.tgz
state: absent
- name: remove golang
file:
state: absent
path: /usr/local/go
- name: remove /root/.cache (pip cache)
file:
state: absent
path: /root/.cache
- name: remove ssh keys
file:
state: absent
path: "{{ item }}"
with_fileglob:
- "/etc/ssh/ssh_host*_key*"
- name: regenerate ssh keys
shell: "dpkg-reconfigure openssh-server"
args:
executable: /bin/bash
handlers:
- name: reload systemd services
systemd:
daemon_reload: yes

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,28 @@
#!/usr/bin/env bash
source /usr/bin/pwnlib
# we need to decrypt something
if is_crypted_mode; then
while ! is_decrypted; do
echo "Waiting for decryption..."
sleep 1
done
fi
# check if wifi driver is bugged
if ! check_brcm; then
if ! reload_brcm; then
echo "Could not reload wifi driver. Reboot"
reboot
fi
sleep 10
fi
# start wlan0mon
start_monitor_interface
if is_auto_mode_no_delete; then
/usr/local/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface wlan0mon
else
/usr/local/bin/bettercap -no-colors -caplet pwnagotchi-manual -iface wlan0mon
fi

View File

@ -0,0 +1,149 @@
#!/usr/bin/env python3
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import parse_qsl
_HTML_FORM_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<title>Decryption</title>
<style>
body {{ text-align: center; padding: 150px; }}
h1 {{ font-size: 50px; }}
body {{ font: 20px Helvetica, sans-serif; color: #333; }}
article {{ display: block; text-align: center; width: 650px; margin: 0 auto;}}
input {{
padding: 12px 20px;
margin: 8px 0;
box-sizing: border-box;
border: 1px solid #ccc;
}}
input[type=password] {{
width: 75%;
font-size: 24px;
}}
input[type=submit] {{
cursor: pointer;
width: 75%;
}}
input[type=submit]:hover {{
background-color: #d9d9d9;
}}
</style>
</head>
<body>
<article>
<h1>Decryption</h1>
<p>Some of your files are encrypted.</p>
<p>Please provide the decryption password.</p>
<div>
<form action="/set-password" method="POST">
{password_fields}
<input type="submit" value="Submit">
</form>
</div>
</article>
</body>
</html>
"""
POST_RESPONSE = """
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
/* Center the loader */
#loader {
position: absolute;
left: 50%;
top: 50%;
z-index: 1;
width: 150px;
height: 150px;
margin: -75px 0 0 -75px;
border: 16px solid #f3f3f3;
border-radius: 50%;
border-top: 16px solid #3498db;
width: 120px;
height: 120px;
-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
}
@-webkit-keyframes spin {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
#myDiv {
display: none;
text-align: center;
}
</style>
<script type="text/javascript">
function checkPwnagotchi() {
var target = 'http://' + document.location.hostname + ':8080/';
var xhr = new XMLHttpRequest();
xhr.open('GET', target);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200 || xhr.status == 401) {
window.location.replace(target);
}else{
setTimeout(checkPwnagotchi, 1000);
}
}
};
xhr.send();
}
setTimeout(checkPwnagotchi, 1000);
</script>
</head>
<body style="margin:0;">
<div id="loader"></div>
</body>
</html>
"""
HTML_FORM = None
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.end_headers()
self.wfile.write(HTML_FORM.encode())
def do_POST(self):
content_length = int(self.headers['Content-Length'])
body = self.rfile.read(content_length)
for mapping, password in parse_qsl(body.decode('UTF-8')):
with open('/tmp/.pwnagotchi-secret-{}'.format(mapping), 'wt') as pwfile:
pwfile.write(password)
self.send_response(200)
self.end_headers()
self.wfile.write(POST_RESPONSE.encode())
with open('/root/.pwnagotchi-crypted') as crypted_file:
mappings = [line.split()[0] for line in crypted_file.readlines()]
fields = ''.join(['<label for="{m}">Passphrase for {m}:</label>\n<input type="password" id="{m}" name="{m}" value=""><br>'.format(m=m)
for m in mappings])
HTML_FORM = _HTML_FORM_TEMPLATE.format(password_fields=fields)
httpd = HTTPServer(('0.0.0.0', 80), SimpleHTTPRequestHandler)
httpd.serve_forever()

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,16 @@
#!/usr/bin/env bash
source /usr/bin/pwnlib
# we need to decrypt something
if is_crypted_mode; then
while ! is_decrypted; do
echo "Waiting for decryption..."
sleep 1
done
fi
if is_auto_mode; then
/usr/local/bin/pwnagotchi
else
/usr/local/bin/pwnagotchi --manual
fi

184
builder/data/32bit/usr/bin/pwnlib Executable file
View File

@ -0,0 +1,184 @@
#!/usr/bin/env bash
# check if brcm is stuck
check_brcm() {
if [[ "$(journalctl -n10 -k --since -5m | grep -c 'brcmf_cfg80211_nexmon_set_channel.*Set Channel failed')" -ge 5 ]]; then
return 1
fi
return 0
}
# reload mod
reload_brcm() {
if ! modprobe -r brcmfmac; then
return 1
fi
sleep 1
if ! modprobe brcmfmac; then
return 1
fi
sleep 2
iw dev wlan0 set power_save off
return 0
}
# starts mon0
start_monitor_interface() {
rfkill unblock all
ifconfig wlan0 up
iw dev wlan0 set power_save off
iw phy "$(iw phy | head -1 | cut -d" " -f2)" interface add wlan0mon type monitor
rfkill unblock all
ifconfig wlan0 down
ifconfig wlan0mon up
iw dev wlan0mon set power_save off
}
# stops mon0
stop_monitor_interface() {
ifconfig wlan0mon down && iw dev wlan0mon del
reload_brcm
ifconfig wlan0 up
}
# returns 0 if the specificed network interface is up
is_interface_up() {
if grep -qi 'up' /sys/class/net/"$1"/operstate; then
return 0
fi
return 1
}
# returns 0 if conditions for AUTO mode are met
is_auto_mode() {
# check override file first
if [ -f /root/.pwnagotchi-manual ]; then
# remove the override file if found
rm -rf /root/.pwnagotchi-manual
return 1
fi
# check override file first
if [ -f /root/.pwnagotchi-auto ]; then
# remove the override file if found
rm -rf /root/.pwnagotchi-auto
return 0
fi
# if usb0 is up, we're in MANU
if is_interface_up usb0; then
return 1
fi
# if eth0 is up (for other boards), we're in MANU
if is_interface_up eth0; then
return 0
fi
# no override, but none of the interfaces is up -> AUTO
return 0
}
# returns 0 if conditions for AUTO mode are met
is_auto_mode_no_delete() {
# check override file first
if [ -f /root/.pwnagotchi-manual ]; then
return 1
fi
# check override file first
if [ -f /root/.pwnagotchi-auto ]; then
return 0
fi
# if usb0 is up, we're in MANU
if is_interface_up usb0; then
return 1
fi
# if eth0 is up (for other boards), we're in MANU
if is_interface_up eth0; then
return 0
fi
# no override, but none of the interfaces is up -> AUTO
return 0
}
# check if we need to decrypt something
is_crypted_mode() {
if [ -f /root/.pwnagotchi-crypted ]; then
return 0
fi
return 1
}
# decryption loop
is_decrypted() {
while read -r mapping container mount; do
# mapping = name the device or file will be mapped to
# container = the luks encrypted device or file
# mount = the mountpoint
# fail if not mounted
if ! mountpoint -q "$mount" >/dev/null 2>&1; then
if [ -f /tmp/.pwnagotchi-secret-"$mapping" ]; then
</tmp/.pwnagotchi-secret-"$mapping" read -r SECRET
if ! test -b /dev/disk/by-id/dm-uuid-*"$(cryptsetup luksUUID "$container" | tr -d -)"*; then
if echo -n "$SECRET" | cryptsetup luksOpen -d- "$container" "$mapping" >/dev/null 2>&1; then
echo "Container decrypted!"
fi
fi
if mount /dev/mapper/"$mapping" "$mount" >/dev/null 2>&1; then
echo "Mounted /dev/mapper/$mapping to $mount"
continue
fi
fi
if ! ip -4 addr show wlan0 | grep inet >/dev/null 2>&1; then
>/dev/null 2>&1 ip addr add 192.168.0.10/24 dev wlan0
fi
if ! pgrep -f decryption-webserver >/dev/null 2>&1; then
>/dev/null 2>&1 decryption-webserver &
fi
if ! pgrep wpa_supplicant >/dev/null 2>&1; then
>/tmp/wpa_supplicant.conf cat <<EOF
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
ap_scan=2
network={
ssid="DECRYPT-ME"
mode=2
key_mgmt=WPA-PSK
psk="pwnagotchi"
frequency=2437
}
EOF
>/dev/null 2>&1 wpa_supplicant -u -s -O -D nl80211 -i wlan0 -c /tmp/wpa_supplicant.conf &
fi
if ! pgrep dnsmasq >/dev/null 2>&1; then
>/dev/null 2>&1 dnsmasq -k -p 53 -h -O "6,192.168.0.10" -A "/#/192.168.0.10" -i wlan0 -K -F 192.168.0.50,192.168.0.60,255.255.255.0,24h &
fi
return 1
fi
done </root/.pwnagotchi-crypted
# overwrite passwords
python3 -c 'print("A"*4096)' | tee /tmp/.pwnagotchi-secret-* >/dev/null
# delete
rm /tmp/.pwnagotchi-secret-*
sync # flush
pkill wpa_supplicant
pkill dnsmasq
pid="$(pgrep -f "decryption-webserver")"
[[ -n "$pid" ]] && kill "$pid"
return 0
}

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 --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,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

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,20 @@
[Unit]
Description=pwnagotchi Deep Reinforcement Learning instrumenting bettercap for WiFI pwning.
Documentation=https://pwnagotchi.org
Wants=network.target
After=pwngrid-peer.service
[Service]
Type=simple
WorkingDirectory=~
ExecStart=/usr/bin/pwnagotchi-launcher
ExecStopPost=/usr/bin/bash -c "if egrep -qi 'personality.clear_on_exit[ =]*true' /etc/pwnagotchi/config.toml ; then /usr/local/bin/pwnagotchi --clear; fi"
Restart=always
RestartSec=30
TasksMax=infinity
LimitNPROC=infinity
StandardOutput=null
StandardError=null
[Install]
WantedBy=multi-user.target

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,33 @@
#!/bin/sh
_hostname=$(hostname)
_version=$(cut -d"'" -f2 < /usr/local/lib/python3.11/dist-packages/pwnagotchi/_version.py)
echo
echo "(☉_☉ ) $_hostname"
echo
echo " Hi! I'm a pwnagotchi $_version, please take good care of me!"
echo " Here are some basic things you need to know to raise me properly!"
echo
echo " If you want to change my configuration, use /etc/pwnagotchi/config.toml"
echo " All plugin config files are located in /etc/pwnagotchi/conf.d/"
echo " Read the readme if you want to use gdrivesync plugin!!"
echo
echo " All the configuration options can be found on /etc/pwnagotchi/default.toml,"
echo " but don't change this file because I will recreate it every time I'm restarted!"
echo
echo " I use oPwnGrid as my main API, you can check stats at https://opwngrid.xyz"
echo
echo " I'm managed by systemd. Here are some basic commands."
echo
echo " If you want to know what I'm doing, you can check my logs with the command"
echo " - pwnlog"
echo " - sudo pwnagotchi --version, to check the current version"
echo " - sudo pwnagotchi --donate, to see how you can donate to this project"
echo " - sudo pwnagotchi --check-update, to see if there is a new version available"
echo
echo " If you want to know if I'm running, you can use"
echo " sudo systemctl status pwnagotchi"
echo
echo " You can restart me using"
echo " pwnkill"
echo
echo " You can learn more about me at https://pwnagotchi.org/"

View File

@ -0,0 +1,100 @@
packer {
required_plugins {
arm = {
version = "1.0.0"
source = "github.com/cdecoux/builder-arm"
}
ansible = {
source = "github.com/hashicorp/ansible"
version = ">= 1.1.1"
}
}
}
variable "pwn_hostname" {
type = string
}
variable "pwn_version" {
type = string
}
source "arm" "rpi64-pwnagotchi" {
file_checksum_url = "https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-12-11/2023-12-11-raspios-bookworm-arm64-lite.img.xz.sha256"
file_urls = ["https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-12-11/2023-12-11-raspios-bookworm-arm64-lite.img.xz"]
file_checksum_type = "sha256"
file_target_extension = "xz"
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
image_path = "../../../pwnagotchi-64bit.img"
qemu_binary_source_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
image_build_method = "resize"
image_size = "9G"
image_type = "dos"
image_partitions {
name = "boot"
type = "c"
start_sector = "8192"
filesystem = "fat"
size = "256M"
mountpoint = "/boot/firmware"
}
image_partitions {
name = "root"
type = "83"
start_sector = "532480"
filesystem = "ext4"
size = "0"
mountpoint = "/"
}
}
# a build block invokes sources and runs provisioning steps on them. The
# documentation for build blocks can be found here:
# https://www.packer.io/docs/from-1.5/blocks/build
build {
name = "Raspberry Pi 64 Pwnagotchi"
sources = ["source.arm.rpi64-pwnagotchi"]
provisioner "file" {
destination = "/usr/bin/"
sources = [
"data/64bit/usr/bin/bettercap-launcher",
"data/64bit/usr/bin/hdmioff",
"data/64bit/usr/bin/hdmion",
"data/64bit/usr/bin/monstart",
"data/64bit/usr/bin/monstop",
"data/64bit/usr/bin/pwnagotchi-launcher",
"data/64bit/usr/bin/pwnlib",
]
}
provisioner "shell" {
inline = ["chmod +x /usr/bin/*"]
}
provisioner "file" {
destination = "/etc/systemd/system/"
sources = [
"data/64bit/etc/systemd/system/bettercap.service",
"data/64bit/etc/systemd/system/pwnagotchi.service",
"data/64bit/etc/systemd/system/pwngrid-peer.service",
]
}
provisioner "file" {
destination = "/etc/update-motd.d/01-motd"
source = "data/64bit/etc/update-motd.d/01-motd"
}
provisioner "shell" {
inline = ["chmod +x /etc/update-motd.d/*"]
}
provisioner "shell" {
inline = ["apt-get -y --allow-releaseinfo-change update", "apt-get -y dist-upgrade", "apt-get install -y --no-install-recommends ansible"]
}
provisioner "ansible-local" {
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
playbook_file = "data/64bit/raspberrypi64.yml"
}
}

View File

@ -0,0 +1,700 @@
---
- hosts:
- 127.0.0.1
gather_facts: true
become: true
vars:
kernel:
min: "6.1"
full: "6.1.0-rpi8-rpi-v8"
full_pi5: "6.1.0-rpi8-rpi-2712"
pwnagotchi:
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi', true) }}"
system:
boot_options:
- "dtoverlay=dwc2"
- "dtoverlay=spi1-3cs"
- "dtparam=i2c1=on"
- "dtparam=i2c_arm=on"
- "dtparam=spi=on"
- "gpu_mem=16"
modules:
- "i2c-dev"
services:
enable:
- bettercap.service
- fstrim.timer
- pwnagotchi.service
- pwngrid-peer.service
disable:
- apt-daily-upgrade.service
- apt-daily-upgrade.timer
- apt-daily.service
- apt-daily.timer
- bluetooth.service
- ifup@wlan0.service
packages:
caplets:
source: "https://github.com/jayofelony/caplets.git"
bettercap:
source: "https://github.com/jayofelony/bettercap.git"
url: "https://github.com/jayofelony/bettercap/releases/download/2.32.4/bettercap-2.32.4.zip"
ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
pwngrid:
source: "https://github.com/jayofelony/pwngrid.git"
url: "https://github.com/jayofelony/pwngrid/releases/download/v1.10.5/pwngrid-1.10.5-aarch64.zip"
apt:
downgrade:
- libpcap-dev_1.9.1-4_arm64.deb
- libpcap0.8-dbg_1.9.1-4_arm64.deb
- libpcap0.8-dev_1.9.1-4_arm64.deb
- libpcap0.8_1.9.1-4_arm64.deb
hold:
- firmware-atheros
- firmware-brcm80211
- firmware-libertas
- firmware-misc-nonfree
- firmware-realtek
- libpcap-dev
- libpcap0.8
- libpcap0.8-dbg
- libpcap0.8-dev
remove:
- avahi-daemon
- dhpys-swapfile
- libcurl-ocaml-dev
- libssl-ocaml-dev
- nfs-common
- triggerhappy
- wpasupplicant
install:
- aircrack-ng
- autoconf
- bc
- bison
- bluez
- build-essential
- curl
- dkms
- dphys-swapfile
- fbi
- firmware-atheros
- firmware-brcm80211
- firmware-libertas
- firmware-misc-nonfree
- firmware-realtek
- flex
- fonts-dejavu
- fonts-dejavu-core
- fonts-dejavu-extra
- fonts-freefont-ttf
- g++
- gawk
- gcc-arm-none-eabi
- git
- hcxtools
- libatlas-base-dev
- libavcodec59
- libavformat59
- libblas-dev
- libbluetooth-dev
- libbz2-dev
- libc-ares-dev
- libc6-dev
- libcap-dev
- libcurl-ocaml-dev
- libdbus-1-dev
- libdbus-glib-1-dev
- libeigen3-dev
- libelf-dev
- libffi-dev
- libfl-dev
- libfuse-dev
- libgdbm-dev
- libgl1-mesa-glx
- libgmp3-dev
- libgstreamer1.0-0
- libhdf5-dev
- liblapack-dev
- libncursesw5-dev
- libnetfilter-queue-dev
- libopenblas-dev
- libopenjp2-7
- libopenmpi-dev
- libopenmpi3
- libpcap-dev
- libraspberrypi-bin
- libraspberrypi-dev
- libraspberrypi-doc
- libraspberrypi0
- libsqlite3-dev
- libssl-dev
- libssl-ocaml-dev
- libswscale5
- libtiff6
- libtool
- libusb-1.0-0-dev
- lsof
- make
- python3-dbus
- python3-flask
- python3-flask-cors
- python3-flaskext.wtf
- python3-gast
- python3-pil
- python3-pip
- python3-pycryptodome
- python3-requests
- python3-scapy
- python3-setuptools
- python3-smbus
- python3-smbus2
- python3-spidev
- python3-tweepy
- python3-werkzeug
- python3-yaml
- qpdf
- raspberrypi-kernel-headers
- rsync
- screen
- tcpdump
- texinfo
- time
- tk-dev
- unzip
- vim
- wget
- wl
- xxd
- zlib1g-dev
environment:
ARCHFLAGS: "-arch aarch64"
tasks:
# First we install packages
- name: install packages
apt:
name: "{{ packages.apt.install }}"
state: present
update_cache: yes
install_recommends: false
# Now we set up /boot/firmware
- name: Create pi user
copy:
dest: /boot/firmware/userconf
content: |
pi:$6$3jNr0GA9KIyt4hmM$efeVIopdMQ8DGgEPCWWlbx3mJJNAYci1lEXGdlky0xPyjqwKNbwTL5SrCcpb4144C4IvzWjn7Iv.QjqmU7iyT/
- name: enable ssh on boot
file:
path: /boot/firmware/ssh
state: touch
- name: adjust /boot/firmware/config.txt
lineinfile:
dest: /boot/firmware/config.txt
insertafter: EOF
line: '{{ item }}'
with_items: "{{ system.boot_options }}"
- name: change root partition
replace:
dest: /boot/firmware/cmdline.txt
backup: no
regexp: "root=PARTUUID=[a-zA-Z0-9\\-]+"
replace: "root=/dev/mmcblk0p2"
- name: configure /boot/firmware/cmdline.txt
lineinfile:
path: /boot/firmware/cmdline.txt
backrefs: True
state: present
backup: no
regexp: '(.*)$'
line: '\1 modules-load=dwc2,g_ether'
- name: change hostname
lineinfile:
dest: /etc/hostname
regexp: '^raspberrypi'
line: "{{pwnagotchi.hostname}}"
state: present
when: lookup('file', '/etc/hostname') == "raspberrypi"
register: hostname
- name: add hostname to /etc/hosts
lineinfile:
dest: /etc/hosts
regexp: '^127\.0\.1\.1[ \t]+raspberrypi'
line: "127.0.1.1\t{{pwnagotchi.hostname}}"
state: present
when: hostname.changed
# Now we disable sap and a2dp, we don't use them on rpi
- name: disable sap plugin for bluetooth.service
lineinfile:
dest: /lib/systemd/system/bluetooth.service
regexp: '^ExecStart=/usr/libexec/bluetooth/bluetoothd$'
line: 'ExecStart=/usr/libexec/bluetooth/bluetoothd --noplugin=sap,a2dp'
state: present
###########################################
#
# libpcap v1.9 - build from source
#
###########################################
# check for presence, then it can re-run in later parts if needed
# use the "make" built in
# install libpcap before bettercap and pwngrid, so they use it
- name: clone libpcap v1.9 from github
git:
repo: 'https://github.com/the-tcpdump-group/libpcap.git'
dest: /usr/local/src/libpcap
version: libpcap-1.9
- name: build and install libpcap into /usr/local/lib
shell: "./configure && make && make install"
args:
executable: /bin/bash
chdir: /usr/local/src/libpcap
- name: remove libpcap build folder
file:
state: absent
path: /usr/local/src/libpcap
- name: create symlink /usr/local/lib/libpcap.so.1.9.1
file:
src: /usr/local/lib/libpcap.so.1.9.1
dest: /usr/local/lib/libpcap.so.0.8
state: link
# install latest hcxtools
- name: clone hcxtools
git:
repo: https://github.com/ZerBea/hcxtools.git
dest: /usr/local/src/hcxtools
- name: install hcxtools
shell: "make && make install"
args:
executable: /bin/bash
chdir: /usr/local/src/hcxtools
- name: remove hcxtools directory
file:
state: absent
path: /usr/local/src/hcxtools
- name: clone nexmon repository
git:
repo: https://github.com/DrSchottky/nexmon.git
dest: /usr/local/src/nexmon
# FIRST WE BUILD DRIVER FOR RPi5
- name: make firmware, RPi5
shell: "source ./setup_env.sh && make"
args:
executable: /bin/bash
chdir: /usr/local/src/nexmon/
environment:
QEMU_UNAME: "{{ kernel.full_pi5 }}"
ARCHFLAGS: "-arch aarch64"
- name: make firmware patch (bcm43455c0), RPi5
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/ && make"
args:
executable: /bin/bash
chdir: /usr/local/src/nexmon/
environment:
QEMU_UNAME: "{{ kernel.full_pi5 }}"
ARCHFLAGS: "-arch aarch64"
- name: copy modified driver, RPi5
copy:
src: "/usr/local/src/nexmon/patches/driver/brcmfmac_{{ kernel.min }}.y-nexmon/brcmfmac.ko"
dest: "/usr/lib/modules/{{ kernel.full_pi5 }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko"
environment:
QEMU_UNAME: "{{ kernel.full_pi5 }}"
ARCHFLAGS: "-arch aarch64"
- name: Delete the modified driver, RPi5
file:
state: absent
path: '/usr/local/src/nexmon/patches/driver/brcmfmac_{{ kernel.min }}.y-nexmon/brcmfmac.ko'
- name: backup original driver, RPi5
command: "mv /usr/lib/modules/{{ kernel.full_pi5 }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz /usr/lib/modules/{{ kernel.full_pi5 }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz.orig"
- name: load brcmfmac drivers
command: "/sbin/depmod {{ kernel.full_pi5 }}"
environment:
QEMU_UNAME: "{{ kernel.full_pi5 }}"
- name: Delete nexmon content & directory
file:
state: absent
path: /usr/local/src/nexmon/
# NOW WE BUILD DRIVERS FOR RPi4, RPizero2w and RPi3
- name: clone nexmon repository
git:
repo: https://github.com/DrSchottky/nexmon.git
dest: /usr/local/src/nexmon
- name: make firmware, RPi4
shell: "source ./setup_env.sh && make"
args:
executable: /bin/bash
chdir: /usr/local/src/nexmon/
environment:
QEMU_UNAME: "{{ kernel.full }}"
ARCHFLAGS: "-arch aarch64"
- name: make firmware patch (bcm43455c0), RPi4
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/ && make"
args:
executable: /bin/bash
chdir: /usr/local/src/nexmon/
environment:
QEMU_UNAME: "{{ kernel.full }}"
ARCHFLAGS: "-arch aarch64"
- name: install new firmware (bcm43455c0), RPi4 RPi5
copy:
src: /usr/local/src/nexmon/patches/bcm43455c0/7_45_206/nexmon/brcmfmac43455-sdio.bin
dest: /usr/lib/firmware/brcm/brcmfmac43455-sdio.bin
follow: true
# NOW WE BUILD DRIVERS FOR RPiZero2W, RPi 3
- name: make firmware patch (bcm43436b0)
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/ && make"
args:
executable: /bin/bash
chdir: /usr/local/src/nexmon/
environment:
QEMU_UNAME: "{{ kernel.full }}"
ARCHFLAGS: "-arch aarch64"
- name: install new firmware (bcm43436b0)
copy:
src: /usr/local/src/nexmon/patches/bcm43436b0/9_88_4_65/nexmon/brcmfmac43436-sdio.bin
dest: /usr/lib/firmware/brcm/brcmfmac43436-sdio.bin
follow: true
- name: make firmware patch (bcm43430a1)
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/ && make"
args:
executable: /bin/bash
chdir: /usr/local/src/nexmon/
environment:
QEMU_UNAME: "{{ kernel.full }}"
ARCHFLAGS: "-arch aarch64"
- name: copy modified driver, RPi4
copy:
src: "/usr/local/src/nexmon/patches/driver/brcmfmac_{{ kernel.min }}.y-nexmon/brcmfmac.ko"
dest: "/usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko"
environment:
QEMU_UNAME: "{{ kernel.full }}"
ARCHFLAGS: "-arch aarch64"
- name: install new firmware (bcm43430a1)
copy:
src: /usr/local/src/nexmon/patches/bcm43430a1/7_45_41_46/nexmon/brcmfmac43430-sdio.bin
dest: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin
follow: true
- name: copy 43430-sdio as 43436s-sdio for the special 43430/1 /2
copy:
src: /usr/lib/firmware/brcm/brcmfmac43430-sdio.bin
dest: /usr/lib/firmware/brcm/brcmfmac43436s-sdio.bin
follow: true
# delete blob files that make nexmon sad
- name: Delete the firmware blob files to avoid some nexmon crashing
file:
state: absent
path: '{{ item }}'
loop:
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.clm_blob
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,model-zero-w.clm_blob
- /usr/lib/firmware/brcm/brcmfmac43430-sdio.raspberrypi,3-model-b.clm_blob
- /usr/lib/firmware/brcm/brcmfmac43430b0-sdio.raspberrypi,model-zero-2-w.clm_blob
- /usr/lib/firmware/brcm/brcmfmac43436-sdio.clm_blob
- /usr/lib/firmware/brcm/brcmfmac43436-sdio.raspberrypi,model-zero-2-w.clm_blob
- /usr/lib/firmware/brcm/brcmfmac43455-sdio.clm_blob
- name: backup original driver
command: "mv /usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz /usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz.orig"
- name: load brcmfmac drivers
command: "/sbin/depmod {{ kernel.full }}"
environment:
QEMU_UNAME: "{{ kernel.full }}"
# To shrink the final image, remove the nexmon directory (takes 2.5G of space) post build and installation
- name: Delete nexmon content & directory
file:
state: absent
path: /usr/local/src/nexmon/
- name: Create custom plugin directory
file:
path: /usr/local/share/pwnagotchi/custom-plugins/
state: directory
- name: Create custom config directory
file:
path: /etc/pwnagotchi/conf.d/
state: directory
- name: clone pwnagotchi repository
git:
repo: https://github.com/jayofelony/pwnagotchi.git
dest: /usr/local/src/pwnagotchi
- name: build pwnagotchi wheel
command: "pip3 install . --no-cache-dir --break-system-packages"
args:
chdir: /usr/local/src/pwnagotchi
- name: remove pwnagotchi folder
file:
state: absent
path: /usr/local/src/pwnagotchi
- name: create /usr/local/share/pwnagotchi/ folder
file:
path: /usr/local/share/pwnagotchi/
state: directory
- name: Install go-1.21
unarchive:
src: https://go.dev/dl/go1.21.5.linux-arm64.tar.gz
dest: /usr/local
remote_src: yes
register: golang
- name: Update .bashrc for go-1.21
blockinfile:
dest: /home/pi/.bashrc
state: present
block: |
export GOPATH=$HOME/go
export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin
when: golang.changed
- name: download pwngrid
git:
repo: "{{ packages.pwngrid.source }}"
dest: /usr/local/src/pwngrid
- name: install pwngrid
shell: "export GOPATH=$HOME/go && export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin && go mod tidy && make && make install"
args:
executable: /bin/bash
chdir: /usr/local/src/pwngrid
- name: remove pwngrid folder
file:
state: absent
path: /usr/local/src/pwngrid
- name: download bettercap
git:
repo: "{{ packages.bettercap.source }}"
dest: /usr/local/src/bettercap
- name: install bettercap 2.32.4
shell: "export GOPATH=$HOME/go && export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin && go mod tidy && make && make install"
args:
executable: /bin/bash
chdir: /usr/local/src/bettercap
- name: remove bettercap folder
file:
state: absent
path: /usr/local/src/bettercap
#- name: download and install bettercap
# unarchive:
# src: "{{ packages.bettercap.url }}"
# dest: /usr/local/bin
# remote_src: yes
# exclude:
# - README.md
# - LICENSE.md
# mode: 0755
- name: clone bettercap caplets
git:
repo: "{{ packages.caplets.source }}"
dest: /tmp/caplets
register: capletsgit
- name: install bettercap caplets
make:
chdir: /tmp/caplets
target: install
when: capletsgit.changed
- name: download and install bettercap ui
unarchive:
src: "{{ packages.bettercap.ui }}"
dest: /usr/local/share/bettercap/
remote_src: yes
mode: 0755
# to always have the bettercap webui available (because why not?)
- name: copy pwnagotchi-manual over pwnagotchi-auto caplet
ansible.builtin.copy:
src: /usr/local/share/bettercap/caplets/pwnagotchi-manual.cap
dest: /usr/local/share/bettercap/caplets/pwnagotchi-auto.cap
force: true
ignore_errors: true
- name: create /etc/pwnagotchi folder
file:
path: /etc/pwnagotchi
state: directory
- name: check if user configuration exists
stat:
path: /etc/pwnagotchi/config.toml
register: user_config
- name: create /etc/pwnagotchi/config.toml
copy:
dest: /etc/pwnagotchi/config.toml
content: |
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
# Example:
# ui.display.enabled = true
# ui.display.type = "waveshare_2"
when: not user_config.stat.exists
- name: Delete motd
file:
state: absent
path: /etc/motd
- name: Delete motd 10-uname
file:
state: absent
path: /etc/update-motd.d/10-uname
- name: Add pwnlog alias
lineinfile:
dest: /home/pi/.bashrc
line: "\nalias pwnlog='tail -f -n300 /etc/pwnagotchi/log/pwn*.log | sed --unbuffered \"s/,[[:digit:]]\\{3\\}\\]//g\" | cut -d \" \" -f 2-'"
insertafter: EOF
- name: Add pwnver alias
lineinfile:
dest: /home/pi/.bashrc
line: "\nalias pwnver='python3 -c \"import pwnagotchi as p; print(p.__version__)\"'"
insertafter: EOF
- name: Add pwnkill alias to restart pwnagotchi with a signal
lineinfile:
dest: /home/pi/.bashrc
line: "\nalias pwnkill='sudo killall -USR1 pwnagotchi'"
insertafter: EOF
- name: add firmware packages to hold
dpkg_selections:
name: "{{ item }}"
selection: hold
with_items: "{{ packages.apt.hold }}"
- name: disable unnecessary services
systemd:
name: "{{ item }}"
state: stopped
enabled: no
with_items: "{{ services.disable }}"
- name: enable services
systemd:
name: "{{ item }}"
enabled: true
state: stopped
with_items: "{{ services.enable }}"
register: enabled
- name: make /root readable, becauase that's where all the files are
file:
path: /root
mode: '755'
- name: fix permissions on /home/pi
file:
path: /home/pi
owner: pi
group: pi
recurse: true
- name: remove pre-collected packages zip
file:
path: /root/go_pkgs.tgz
state: absent
- name: remove /root/go folder
file:
state: absent
path: /root/go
- name: remove /usr/local/go folder
file:
state: absent
path: /usr/local/go
- name: remove pip cache
file:
state: absent
path: /root/.cache/pip
- name: remove ssh keys
file:
state: absent
path: "{{ item }}"
with_fileglob:
- "/etc/ssh/ssh_host*_key*"
- name: regenerate ssh keys
shell: "dpkg-reconfigure openssh-server"
args:
executable: /bin/bash
# Now we remove packages
- name: remove unnecessary apt packages
apt:
name: "{{ packages.apt.remove }}"
state: absent
purge: yes
register: removed
- name: remove dependencies that are no longer required
apt:
autoremove: yes
when: removed.changed
- name: clean apt cache
apt:
autoclean: true
when: removed.changed
handlers:
- name: reload systemd services
systemd:
daemon_reload: yes
when: enabled.changed

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,19 @@
#!/usr/bin/env bash
source /usr/bin/pwnlib
# we need to decrypt something
if is_crypted_mode; then
while ! is_decrypted; do
echo "Waiting for decryption..."
sleep 1
done
fi
# start mon0
start_monitor_interface
if is_auto_mode_no_delete; then
/usr/local/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface wlan0mon
else
/usr/local/bin/bettercap -no-colors -caplet pwnagotchi-manual -iface wlan0mon
fi

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()

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,16 @@
#!/usr/bin/env bash
source /usr/bin/pwnlib
# we need to decrypt something
if is_crypted_mode; then
while ! is_decrypted; do
echo "Waiting for decryption..."
sleep 1
done
fi
if is_auto_mode; then
/usr/local/bin/pwnagotchi
else
/usr/local/bin/pwnagotchi --manual
fi

191
builder/data/64bit/usr/bin/pwnlib Executable file
View File

@ -0,0 +1,191 @@
#!/usr/bin/env bash
# well ... it blinks the led
blink_led() {
# shellcheck disable=SC2034
for i in $(seq 1 "$1"); do
echo 0 >/sys/class/leds/led0/brightness
sleep 0.3
echo 1 >/sys/class/leds/led0/brightness
sleep 0.3
done
echo 0 >/sys/class/leds/led0/brightness
sleep 0.3
}
# reload mod
reload_brcm() {
if ! modprobe -r brcmfmac; then
return 1
fi
sleep 1
if ! modprobe brcmfmac; then
return 1
fi
sleep 2
iw dev wlan0 set power_save off
return 0
}
# starts mon0
start_monitor_interface() {
rfkill unblock all
ifconfig wlan0 up
sleep 3
iw dev wlan0 set power_save off
iw phy "$(iw phy | head -1 | cut -d" " -f2)" interface add wlan0mon type monitor
sleep 2
rfkill unblock all
ifconfig wlan0 down
ifconfig wlan0mon up
iw dev wlan0mon set power_save off
}
# stops mon0
stop_monitor_interface() {
ifconfig wlan0mon down && iw dev wlan0mon del
reload_brcm
ifconfig wlan0 up
}
# returns 0 if the specified network interface is up
is_interface_up() {
if grep -qi 'up' /sys/class/net/"$1"/operstate; then
return 0
fi
return 1
}
# returns 0 if conditions for AUTO mode are met
is_auto_mode() {
# check override file first
if [ -f /root/.pwnagotchi-manual ]; then
# remove the override file if found
rm -rf /root/.pwnagotchi-manual
return 1
fi
# check override file first
if [ -f /root/.pwnagotchi-auto ]; then
# remove the override file if found
rm -rf /root/.pwnagotchi-auto
return 0
fi
# if usb0 is up, we're in MANU
if is_interface_up usb0; then
return 1
fi
# if eth0 is up (for other boards), we're in MANU
if is_interface_up eth0; then
return 0
fi
# no override, but none of the interfaces is up -> AUTO
return 0
}
# returns 0 if conditions for AUTO mode are met
is_auto_mode_no_delete() {
# check override file first
if [ -f /root/.pwnagotchi-manual ]; then
return 1
fi
# check override file first
if [ -f /root/.pwnagotchi-auto ]; then
return 0
fi
# if usb0 is up, we're in MANU
if is_interface_up usb0; then
return 1
fi
# if eth0 is up (for other boards), we're in MANU
if is_interface_up eth0; then
return 0
fi
# no override, but none of the interfaces is up -> AUTO
return 0
}
# check if we need to decrypt something
is_crypted_mode() {
if [ -f /root/.pwnagotchi-crypted ]; then
return 0
fi
return 1
}
# decryption loop
is_decrypted() {
while read -r mapping container mount; do
# mapping = name the device or file will be mapped to
# container = the luks encrypted device or file
# mount = the mountpoint
# fail if not mounted
if ! mountpoint -q "$mount" >/dev/null 2>&1; then
if [ -f /tmp/.pwnagotchi-secret-"$mapping" ]; then
</tmp/.pwnagotchi-secret-"$mapping" read -r SECRET
if ! test -b /dev/disk/by-id/dm-uuid-*"$(cryptsetup luksUUID "$container" | tr -d -)"*; then
if echo -n "$SECRET" | cryptsetup luksOpen -d- "$container" "$mapping" >/dev/null 2>&1; then
echo "Container decrypted!"
fi
fi
if mount /dev/mapper/"$mapping" "$mount" >/dev/null 2>&1; then
echo "Mounted /dev/mapper/$mapping to $mount"
continue
fi
fi
if ! ip -4 addr show wlan0 | grep inet >/dev/null 2>&1; then
>/dev/null 2>&1 ip addr add 192.168.0.10/24 dev wlan0
fi
if ! pgrep -f decryption-webserver >/dev/null 2>&1; then
>/dev/null 2>&1 decryption-webserver &
fi
if ! pgrep wpa_supplicant >/dev/null 2>&1; then
>/tmp/wpa_supplicant.conf cat <<EOF
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
ap_scan=2
network={
ssid="DECRYPT-ME"
mode=2
key_mgmt=WPA-PSK
psk="pwnagotchi"
frequency=2437
}
EOF
>/dev/null 2>&1 wpa_supplicant -u -s -O -D nl80211 -i wlan0 -c /tmp/wpa_supplicant.conf &
fi
if ! pgrep dnsmasq >/dev/null 2>&1; then
>/dev/null 2>&1 dnsmasq -k -p 53 -h -O "6,192.168.0.10" -A "/#/192.168.0.10" -i wlan0 -K -F 192.168.0.50,192.168.0.60,255.255.255.0,24h &
fi
return 1
fi
done </root/.pwnagotchi-crypted
# overwrite passwords
python3 -c 'print("A"*4096)' | tee /tmp/.pwnagotchi-secret-* >/dev/null
# delete
rm /tmp/.pwnagotchi-secret-*
sync # flush
pkill wpa_supplicant
pkill dnsmasq
pid="$(pgrep -f "decryption-webserver")"
[[ -n "$pid" ]] && kill "$pid"
return 0
}

View File

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

View File

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

View File

@ -4,9 +4,7 @@ import os
import re import re
import logging import logging
import asyncio import asyncio
#import _thread import _thread
import threading
import subprocess
import pwnagotchi import pwnagotchi
import pwnagotchi.utils as utils import pwnagotchi.utils as utils
@ -16,11 +14,12 @@ from pwnagotchi.automata import Automata
from pwnagotchi.log import LastSession from pwnagotchi.log import LastSession
from pwnagotchi.bettercap import Client from pwnagotchi.bettercap import Client
from pwnagotchi.mesh.utils import AsyncAdvertiser from pwnagotchi.mesh.utils import AsyncAdvertiser
from pwnagotchi.ai.train import AsyncTrainer
RECOVERY_DATA_FILE = '/root/.pwnagotchi-recovery' RECOVERY_DATA_FILE = '/root/.pwnagotchi-recovery'
class Agent(Client, Automata, AsyncAdvertiser): class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
def __init__(self, view, config, keypair): def __init__(self, view, config, keypair):
Client.__init__(self, Client.__init__(self,
"127.0.0.1" if "hostname" not in config['bettercap'] else config['bettercap']['hostname'], "127.0.0.1" if "hostname" not in config['bettercap'] else config['bettercap']['hostname'],
@ -30,6 +29,7 @@ class Agent(Client, Automata, AsyncAdvertiser):
"pwnagotchi" if "password" not in config['bettercap'] else config['bettercap']['password']) "pwnagotchi" if "password" not in config['bettercap'] else config['bettercap']['password'])
Automata.__init__(self, config, view) Automata.__init__(self, config, view)
AsyncAdvertiser.__init__(self, config, view, keypair) AsyncAdvertiser.__init__(self, config, view, keypair)
AsyncTrainer.__init__(self, config)
self._started_at = time.time() self._started_at = time.time()
self._current_channel = 0 self._current_channel = 0
@ -129,6 +129,7 @@ class Agent(Client, Automata, AsyncAdvertiser):
time.sleep(1) time.sleep(1)
def start(self): def start(self):
self.start_ai()
self._wait_bettercap() self._wait_bettercap()
self.setup_events() self.setup_events()
self.set_starting() self.set_starting()
@ -253,7 +254,7 @@ class Agent(Client, Automata, AsyncAdvertiser):
txt = '%d (%d)' % (len(self._handshakes), tot) txt = '%d (%d)' % (len(self._handshakes), tot)
if self._last_pwnd is not None: if self._last_pwnd is not None:
txt += ' [%s]' % self._last_pwnd txt += ' [%s]' % self._last_pwnd[:11] # So it doesn't overlap with fix_brcmfmac_plugin
self._view.set('shakes', txt) self._view.set('shakes', txt)
@ -268,9 +269,9 @@ class Agent(Client, Automata, AsyncAdvertiser):
self._save_recovery_data() self._save_recovery_data()
pwnagotchi.reboot() pwnagotchi.reboot()
def _restart(self, mode='AUTO'): def _restart(self):
self._save_recovery_data() self._save_recovery_data()
pwnagotchi.restart(mode) pwnagotchi.restart("AUTO")
def _save_recovery_data(self): def _save_recovery_data(self):
logging.warning("writing recovery data to %s ...", RECOVERY_DATA_FILE) logging.warning("writing recovery data to %s ...", RECOVERY_DATA_FILE)
@ -303,8 +304,7 @@ class Agent(Client, Automata, AsyncAdvertiser):
raise raise
def start_session_fetcher(self): def start_session_fetcher(self):
#_thread.start_new_thread(self._fetch_stats, ()) _thread.start_new_thread(self._fetch_stats, ())
threading.Thread(target=self._fetch_stats, args=(), name="Session Fetcher", daemon=True).start()
def _fetch_stats(self): def _fetch_stats(self):
while True: while True:
@ -387,8 +387,7 @@ class Agent(Client, Automata, AsyncAdvertiser):
def start_event_polling(self): def start_event_polling(self):
# start a thread and pass in the mainloop # start a thread and pass in the mainloop
#_thread.start_new_thread(self._event_poller, (asyncio.get_event_loop(),)) _thread.start_new_thread(self._event_poller, (asyncio.get_event_loop(),))
threading.Thread(target=self._event_poller, args=(asyncio.get_event_loop(),), name="Event Polling", daemon=True).start()
def is_module_running(self, module): def is_module_running(self, module):
s = self.session() s = self.session()

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 gym
from gym import spaces
import numpy as np
import pwnagotchi.ai.featurizer as featurizer
import pwnagotchi.ai.reward as reward
from pwnagotchi.ai.parameter import Parameter
class Environment(gym.Env):
render_mode = "human"
metadata = {'render_modes': ['human']}
params = [
Parameter('min_rssi', min_value=-200, max_value=-50),
Parameter('ap_ttl', min_value=30, max_value=600),
Parameter('sta_ttl', min_value=60, max_value=300),
Parameter('recon_time', min_value=5, max_value=60),
Parameter('max_inactive_scale', min_value=3, max_value=10),
Parameter('recon_inactive_multiplier', min_value=1, max_value=3),
Parameter('hop_recon_time', min_value=5, max_value=60),
Parameter('min_recon_time', min_value=1, max_value=30),
Parameter('max_interactions', min_value=1, max_value=25),
Parameter('max_misses_for_recon', min_value=3, max_value=10),
Parameter('excited_num_epochs', min_value=5, max_value=30),
Parameter('bored_num_epochs', min_value=5, max_value=30),
Parameter('sad_num_epochs', min_value=5, max_value=30),
]
def __init__(self, agent, epoch):
super(Environment, self).__init__()
self._agent = agent
self._epoch = epoch
self._epoch_num = 0
self._last_render = None
channels = agent.supported_channels()
Environment.params += [
Parameter('_channel_%d' % ch, min_value=0, max_value=1, meta=ch + 1) for ch in
range(featurizer.histogram_size) if ch + 1 in channels
]
self.last = {
'reward': 0.0,
'observation': None,
'policy': None,
'params': {},
'state': None,
'state_v': None
}
self.action_space = spaces.MultiDiscrete([p.space_size() for p in Environment.params if p.trainable])
self.observation_space = spaces.Box(low=0, high=1, shape=featurizer.shape, dtype=np.float32)
self.reward_range = reward.range
@staticmethod
def policy_size():
return len(list(p for p in Environment.params if p.trainable))
@staticmethod
def policy_to_params(policy):
num = len(policy)
params = {}
assert len(Environment.params) == num
channels = []
for i in range(num):
param = Environment.params[i]
if '_channel' not in param.name:
params[param.name] = param.to_param_value(policy[i])
else:
has_chan = param.to_param_value(policy[i])
# print("%s policy:%s bool:%s" % (param.name, policy[i], has_chan))
chan = param.meta
if has_chan:
channels.append(chan)
params['channels'] = channels
return params
def _next_epoch(self):
logging.debug("[ai] waiting for epoch to finish ...")
return self._epoch.wait_for_epoch_data()
def _apply_policy(self, policy):
new_params = Environment.policy_to_params(policy)
self.last['policy'] = policy
self.last['params'] = new_params
self._agent.on_ai_policy(new_params)
def step(self, policy):
# create the parameters from the policy and update
# them in the algorithm
self._apply_policy(policy)
self._epoch_num += 1
# wait for the algorithm to run with the new parameters
state = self._next_epoch()
self.last['reward'] = state['reward']
self.last['state'] = state
self.last['state_v'] = featurizer.featurize(state, self._epoch_num)
self._agent.on_ai_step()
return self.last['state_v'], self.last['reward'], not self._agent.is_training(), {}
def reset(self):
# logging.info("[ai] resetting environment ...")
self._epoch_num = 0
state = self._next_epoch()
self.last['state'] = state
self.last['state_v'] = featurizer.featurize(state, 1)
return self.last['state_v']
def _render_histogram(self, hist):
for ch in range(featurizer.histogram_size):
if hist[ch]:
logging.info(" CH %d: %s" % (ch + 1, hist[ch]))
def render(self, mode='human', close=False, force=False):
# when using a vectorialized environment, render gets called twice
# avoid rendering the same data
if self._last_render == self._epoch_num:
return
if not self._agent.is_training() and not force:
return
self._last_render = self._epoch_num
logging.info("[AI] --- training epoch %d/%d ---" % (self._epoch_num, self._agent.training_epochs()))
logging.info("[AI] REWARD: %f" % self.last['reward'])
logging.debug(
"[AI] policy: %s" % ', '.join("%s:%s" % (name, value) for name, value in self.last['params'].items()))
logging.info("[AI] observation:")
for name, value in self.last['state'].items():
if 'histogram' in name:
logging.info(" %s" % name.replace('_histogram', ''))
self._render_histogram(value)

View File

@ -0,0 +1,30 @@
from gym import spaces
class Parameter(object):
def __init__(self, name, value=0.0, min_value=0, max_value=2, meta=None, trainable=True):
self.name = name
self.trainable = trainable
self.meta = meta
self.value = value
self.min_value = min_value
self.max_value = max_value + 1
# gymnasium.space.Discrete is within [0, 1, 2, ..., n-1]
if self.min_value < 0:
self.scale_factor = abs(self.min_value)
elif self.min_value > 0:
self.scale_factor = -self.min_value
else:
self.scale_factor = 0
def space_size(self):
return self.max_value + self.scale_factor
def space(self):
return spaces.Discrete(self.max_value + self.scale_factor)
def to_param_value(self, policy_v):
self.value = policy_v - self.scale_factor
assert self.min_value <= self.value <= self.max_value
return int(self.value)

View File

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

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

@ -0,0 +1,197 @@
import _thread
import threading
import time
import random
import os
import json
import logging
import pwnagotchi.plugins as plugins
import pwnagotchi.ai as ai
class Stats(object):
def __init__(self, path, events_receiver):
self._lock = threading.Lock()
self._receiver = events_receiver
self.path = path
self.born_at = time.time()
# total epochs lived (trained + just eval)
self.epochs_lived = 0
# total training epochs
self.epochs_trained = 0
self.worst_reward = 0.0
self.best_reward = 0.0
self.load()
def on_epoch(self, data, training):
best_r = False
worst_r = False
with self._lock:
reward = data['reward']
if reward < self.worst_reward:
self.worst_reward = reward
worst_r = True
elif reward > self.best_reward:
best_r = True
self.best_reward = reward
self.epochs_lived += 1
if training:
self.epochs_trained += 1
self.save()
if best_r:
self._receiver.on_ai_best_reward(reward)
elif worst_r:
self._receiver.on_ai_worst_reward(reward)
def load(self):
with self._lock:
if os.path.exists(self.path) and os.path.getsize(self.path) > 0:
logging.info("[AI] loading %s" % self.path)
with open(self.path, 'rt') as fp:
obj = json.load(fp)
self.born_at = obj['born_at']
self.epochs_lived, self.epochs_trained = obj['epochs_lived'], obj['epochs_trained']
self.best_reward, self.worst_reward = obj['rewards']['best'], obj['rewards']['worst']
def save(self):
with self._lock:
logging.info("[AI] saving %s" % self.path)
data = json.dumps({
'born_at': self.born_at,
'epochs_lived': self.epochs_lived,
'epochs_trained': self.epochs_trained,
'rewards': {
'best': self.best_reward,
'worst': self.worst_reward
}
})
temp = "%s.tmp" % self.path
back = "%s.bak" % self.path
with open(temp, 'wt') as fp:
fp.write(data)
if os.path.isfile(self.path):
os.replace(self.path, back)
os.replace(temp, self.path)
class AsyncTrainer(object):
def __init__(self, config):
self._config = config
self._model = None
self._is_training = False
self._training_epochs = 0
self._nn_path = self._config['ai']['path']
self._stats = Stats("%s.json" % os.path.splitext(self._nn_path)[0], self)
def set_training(self, training, for_epochs=0):
self._is_training = training
self._training_epochs = for_epochs
if training:
plugins.on('ai_training_start', self, for_epochs)
else:
plugins.on('ai_training_end', self)
def is_training(self):
return self._is_training
def training_epochs(self):
return self._training_epochs
def start_ai(self):
_thread.start_new_thread(self._ai_worker, ())
def _save_ai(self):
logging.info("[AI] saving model to %s ..." % self._nn_path)
temp = "%s.tmp" % self._nn_path
self._model.save(temp)
os.replace(temp, self._nn_path)
def on_ai_step(self):
self._model.env.render()
if self._is_training:
self._save_ai()
self._stats.on_epoch(self._epoch.data(), self._is_training)
def on_ai_training_step(self, _locals, _globals):
self._model.env.render()
plugins.on('ai_training_step', self, _locals, _globals)
def on_ai_policy(self, new_params):
plugins.on('ai_policy', self, new_params)
logging.info("[AI] setting new policy:")
for name, value in new_params.items():
if name in self._config['personality']:
curr_value = self._config['personality'][name]
if curr_value != value:
logging.info("[AI] ! %s: %s -> %s" % (name, curr_value, value))
self._config['personality'][name] = value
else:
logging.error("[AI] param %s not in personality configuration!" % name)
self.run('set wifi.ap.ttl %d' % self._config['personality']['ap_ttl'])
self.run('set wifi.sta.ttl %d' % self._config['personality']['sta_ttl'])
self.run('set wifi.rssi.min %d' % self._config['personality']['min_rssi'])
def on_ai_ready(self):
self._view.on_ai_ready()
plugins.on('ai_ready', self)
def on_ai_best_reward(self, r):
logging.info("[AI] best reward so far: %s" % r)
self._view.on_motivated(r)
plugins.on('ai_best_reward', self, r)
def on_ai_worst_reward(self, r):
logging.info("[AI] worst reward so far: %s" % r)
self._view.on_demotivated(r)
plugins.on('ai_worst_reward', self, r)
def _ai_worker(self):
self._model = ai.load(self._config, self, self._epoch)
if self._model:
self.on_ai_ready()
epochs_per_episode = self._config['ai']['epochs_per_episode']
obs = None
while True:
self._model.env.render()
# enter in training mode?
if random.random() > self._config['ai']['laziness']:
logging.info("[AI] learning for %d epochs ..." % epochs_per_episode)
try:
self.set_training(True, epochs_per_episode)
# back up brain file before starting new training set
if os.path.isfile(self._nn_path):
back = "%s.bak" % self._nn_path
os.replace(self._nn_path, back)
self._view.set("mode", " AI")
self._model.learn(total_timesteps=epochs_per_episode, callback=self.on_ai_training_step)
except Exception as e:
logging.exception("[AI] error while training (%s)", e)
finally:
self.set_training(False)
obs = self._model.env.reset()
# init the first time
elif obs is None:
obs = self._model.env.reset()
# run the inference
action, _ = self._model.predict(obs)
obs, _, _, _ = self._model.env.step(action)

16
pwnagotchi/ai/utils.py Normal file
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 import pwnagotchi.plugins as plugins
from pwnagotchi.ai.epoch import Epoch from pwnagotchi.ai.epoch import Epoch
import os
# basic mood system # basic mood system
@ -137,6 +136,7 @@ class Automata(object):
self.set_grateful() self.set_grateful()
plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data()) plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data())
if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']: 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) logging.critical("%d epochs without visible access points -> restarting ...", self._epoch.blind_for)
self._restart() self._restart()

View File

@ -100,6 +100,7 @@ class Client(object):
await asyncio.sleep(sleep_time) await asyncio.sleep(sleep_time)
continue continue
except OSError: except OSError:
sleep_time = min_sleep + max_sleep * random.random()
logging.warning('connection to the bettercap endpoint failed...') logging.warning('connection to the bettercap endpoint failed...')
pwnagotchi.restart("AUTO") pwnagotchi.restart("AUTO")

View File

@ -1,342 +0,0 @@
import logging
import argparse
import time
import signal
import sys
import toml
import requests
import os
import re
import pwnagotchi
from pwnagotchi import utils
from pwnagotchi.google import cmd as google_cmd
from pwnagotchi.plugins import cmd as plugins_cmd
from pwnagotchi import log
from pwnagotchi import fs
from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple
def pwnagotchi_cli():
def do_clear(display):
logging.info("clearing the display ...")
display.clear()
sys.exit(0)
def do_manual_mode(agent):
logging.info("entering manual mode ...")
agent.mode = 'manual'
agent.last_session.parse(agent.view(), args.skip_session)
if not args.skip_session:
logging.info(
"the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % (
agent.last_session.duration_human,
agent.last_session.epochs,
agent.last_session.train_epochs,
agent.last_session.avg_reward,
agent.last_session.min_reward,
agent.last_session.max_reward))
while True:
display.on_manual_mode(agent.last_session)
time.sleep(5)
if grid.is_connected():
plugins.on('internet_available', agent)
def do_auto_mode(agent):
logging.info("entering auto mode ...")
agent.mode = 'auto'
agent.last_session.parse(agent.view(), args.skip_session) # show stats in AUTO
agent.start()
while True:
try:
# recon on all channels
agent.recon()
# get nearby access points grouped by channel
channels = agent.get_access_points_by_channel()
# for each channel
for ch, aps in channels:
time.sleep(1)
agent.set_channel(ch)
if not agent.is_stale() and agent.any_activity():
logging.info("%d access points on channel %d" % (len(aps), ch))
# for each ap on this channel
for ap in aps:
# send an association frame in order to get for a PMKID
agent.associate(ap)
# deauth all client stations in order to get a full handshake
for sta in ap['clients']:
agent.deauth(ap, sta)
time.sleep(1) # delay to not trigger nexmon firmware bugs
# An interesting effect of this:
#
# From Pwnagotchi's perspective, the more new access points
# and / or client stations nearby, the longer one epoch of
# its relative time will take ... basically, in Pwnagotchi's universe,
# Wi-Fi electromagnetic fields affect time like gravitational fields
# affect ours ... neat ^_^
agent.next_epoch()
if grid.is_connected():
plugins.on('internet_available', agent)
except Exception as e:
if str(e).find("wifi.interface not set") > 0:
logging.exception("main loop exception due to unavailable wifi device, likely programmatically disabled (%s)", e)
logging.info("sleeping 60 seconds then advancing to next epoch to allow for cleanup code to trigger")
time.sleep(60)
agent.next_epoch()
else:
logging.exception("main loop exception (%s)", e)
def add_parsers(parser):
"""
Adds the plugins and google subcommands
"""
subparsers = parser.add_subparsers()
# Add parsers from plugins_cmd
plugins_cmd.add_parsers(subparsers)
# Add parsers from google_cmd
google_cmd.add_parsers(subparsers)
parser = argparse.ArgumentParser(prog="pwnagotchi")
# pwnagotchi --help
parser.add_argument('-C', '--config', action='store', dest='config', default='/etc/pwnagotchi/default.toml',
help='Main configuration file.')
parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/etc/pwnagotchi/config.toml',
help='If this file exists, configuration will be merged and this will override default values.')
parser.add_argument('--manual', dest="do_manual", action="store_true", default=False, help="Manual mode.")
parser.add_argument('--skip-session', dest="skip_session", action="store_true", default=False,
help="Skip last session parsing in manual mode.")
parser.add_argument('--clear', dest="do_clear", action="store_true", default=False,
help="Clear the ePaper display and exit.")
parser.add_argument('--debug', dest="debug", action="store_true", default=False,
help="Enable debug logs.")
parser.add_argument('--version', dest="version", action="store_true", default=False,
help="Print the version.")
parser.add_argument('--print-config', dest="print_config", action="store_true", default=False,
help="Print the configuration.")
# Jayofelony added these
parser.add_argument('--wizard', dest="wizard", action="store_true", default=False,
help="Interactive installation of your personal configuration.")
parser.add_argument('--check-update', dest="check_update", action="store_true", default=False,
help="Check for updates on Pwnagotchi. And tells current version.")
parser.add_argument('--donate', dest="donate", action="store_true", default=False,
help="How to donate to this project.")
# pwnagotchi plugins --help
add_parsers(parser)
args = parser.parse_args()
if plugins_cmd.used_plugin_cmd(args):
config = utils.load_config(args)
log.setup_logging(args, config)
rc = plugins_cmd.handle_cmd(args, config)
sys.exit(rc)
if google_cmd.used_google_cmd(args):
config = utils.load_config(args)
log.setup_logging(args, config)
rc = google_cmd.handle_cmd(args)
sys.exit(rc)
if args.version:
print(pwnagotchi.__version__)
sys.exit(0)
if args.wizard:
def is_valid_hostname(hostname):
if len(hostname) > 255:
return False
if hostname[-1] == ".":
hostname = hostname[:-1] # strip exactly one dot from the right, if present
allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
return all(allowed.match(x) for x in hostname.split("."))
pwn_restore = input("Do you want to restore the previous configuration?\n\n"
"[Y/N]: ")
if pwn_restore in ('y', 'yes'):
os.system("cp -f /etc/pwnagotchi/config.toml.bak /etc/pwnagotchi/config.toml")
print("Your previous configuration is restored, and I will restart in 5 seconds.")
time.sleep(5)
os.system("service pwnagotchi restart")
else:
pwn_check = input("This will create a new configuration file and overwrite your current backup, are you sure?\n\n"
"[Y/N]: ")
if pwn_check.lower() in ('y', 'yes'):
os.system("mv -f /etc/pwnagotchi/config.toml /etc/pwnagotchi/config.toml.bak")
with open("/etc/pwnagotchi/config.toml", "a+") as f:
f.write("# Do not edit this file if you do not know what you are doing!!!\n\n")
# Set pwnagotchi name
print("Welcome to the interactive installation of your personal Pwnagotchi configuration!\n"
"My name is Jayofelony, how may I call you?\n\n")
pwn_name = input("Pwnagotchi name (no spaces): ")
if pwn_name == "":
pwn_name = "Pwnagotchi"
print("I shall go by Pwnagotchi from now on!")
pwn_name = f"main.name = \"{pwn_name}\"\n"
f.write(pwn_name)
else:
if is_valid_hostname(pwn_name):
print(f"I shall go by {pwn_name} from now on!")
pwn_name = f"main.name = \"{pwn_name}\"\n"
f.write(pwn_name)
else:
print("You have chosen an invalid name. Please start over.")
exit()
pwn_whitelist = input("How many networks do you want to whitelist? "
"We will also ask a MAC for each network?\n"
"Each SSID and BSSID count as 1 network. \n\n"
"Be sure to use digits as your answer.\n\n"
"Amount of networks: ")
if int(pwn_whitelist) > 0:
f.write("main.whitelist = [\n")
for x in range(int(pwn_whitelist)):
ssid = input("SSID (Name): ")
bssid = input("BSSID (MAC): ")
f.write(f"\t\"{ssid}\",\n")
f.write(f"\t\"{bssid}\",\n")
f.write("]\n")
# set bluetooth tether
pwn_bluetooth = input("Do you want to enable BT-Tether?\n\n"
"[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"
"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")
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")
# set up display settings
pwn_display_enabled = input("Do you want to enable a display?\n\n"
"[Y/N]: ")
if pwn_display_enabled.lower() in ('y', 'yes'):
f.write("ui.display.enabled = true\n")
pwn_display_type = input("What display do you use?\n\n"
"Be sure to check for the correct display type @ \n"
"https://github.com/jayofelony/pwnagotchi/blob/master/pwnagotchi/utils.py#L240-L501\n\n"
"Display type: ")
if pwn_display_type != "":
f.write(f"ui.display.type = \"{pwn_display_type}\"\n")
pwn_display_invert = input("Do you want to invert the display colors?\n"
"N = Black background\n"
"Y = White background\n\n"
"[Y/N]: ")
if pwn_display_invert.lower() in ('y', 'yes'):
f.write("ui.invert = true\n")
f.close()
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.")
else:
print("Your configuration is done, and I will restart in 5 seconds.")
time.sleep(5)
os.system("service pwnagotchi restart")
else:
print("Ok, doing nothing.")
sys.exit(0)
if args.donate:
print("Donations can be made @ \n "
"https://github.com/sponsors/jayofelony \n\n"
"But only if you really want to!")
sys.exit(0)
if args.check_update:
resp = requests.get("https://api.github.com/repos/jayofelony/pwnagotchi/releases/latest")
latest = resp.json()
latest_ver = latest['tag_name'].replace('v', '')
local = version_to_tuple(pwnagotchi.__version__)
remote = version_to_tuple(latest_ver)
if remote > local:
user_input = input("There is a new version available! Update from v%s to v%s?\n[Y/N] " % (pwnagotchi.__version__, latest_ver))
# input validation
if user_input.lower() in ('y', 'yes'):
if os.path.exists('/root/.auto-update'):
os.system("rm /root/.auto-update && systemctl restart pwnagotchi")
else:
logging.error("You should make sure auto-update is enabled!")
print("Okay, give me a couple minutes. Just watch pwnlog while you wait.")
elif user_input.lower() in ('n', 'no'): # using this elif for readability
print("Okay, guess not!")
else:
print("You are currently on the latest release, v%s." % pwnagotchi.__version__)
sys.exit(0)
config = utils.load_config(args)
if args.print_config:
print(toml.dumps(config, encoder=DottedTomlEncoder()))
sys.exit(0)
from pwnagotchi.identity import KeyPair
from pwnagotchi.agent import Agent
from pwnagotchi.ui import fonts
from pwnagotchi.ui.display import Display
from pwnagotchi import grid
from pwnagotchi import plugins
pwnagotchi.config = config
fs.setup_mounts(config)
log.setup_logging(args, config)
fonts.init(config)
pwnagotchi.set_name(config['main']['name'])
plugins.load(config)
display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()})
if args.do_clear:
do_clear(display)
sys.exit(0)
agent = Agent(view=display, config=config, keypair=KeyPair(view=display))
def usr1_handler(*unused):
logging.info('Received USR1 signal. Restart process ...')
agent._restart("MANU" if args.do_manual else "AUTO")
signal.signal(signal.SIGUSR1, usr1_handler)
if args.do_manual:
do_manual_mode(agent)
else:
do_auto_mode(agent)
if __name__ == '__main__':
pwnagotchi_cli()

View File

@ -9,25 +9,45 @@ main.whitelist = [
main.confd = "/etc/pwnagotchi/conf.d/" main.confd = "/etc/pwnagotchi/conf.d/"
main.custom_plugin_repos = [ main.custom_plugin_repos = [
"https://github.com/jayofelony/pwnagotchi-torch-plugins/archive/master.zip", "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/Sniffleupagus/pwnagotchi_plugins/archive/master.zip",
"https://github.com/NeonLightning/pwny/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/marbasec/UPSLite_Plugin_1_3/archive/master.zip"
"https://github.com/wpa-2/Pwnagotchi-Plugins/archive/master.zip"
] ]
main.custom_plugins = "/usr/local/share/pwnagotchi/custom-plugins/" main.custom_plugins = "/usr/local/share/pwnagotchi/custom-plugins/"
main.plugins.auto-tune.enabled = true main.plugins.auto-update.enabled = true
main.plugins.auto-update.install = true
main.plugins.auto-update.enabled = false
main.plugins.auto-update.install = false
main.plugins.auto-update.interval = 1 main.plugins.auto-update.interval = 1
main.plugins.bt-tether.enabled = false 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 = "" # Configuration for Android Phone
main.plugins.bt-tether.phone = "" # android or ios main.plugins.bt-tether.devices.android-phone.enabled = false
main.plugins.bt-tether.ip = "" # 192.168.44.2 android / 172.20.10.2 ios main.plugins.bt-tether.devices.android-phone.search_order = 1
main.plugins.bt-tether.devices.android-phone.mac = "" # Bluetooth MAC address of the Android phone
main.plugins.bt-tether.devices.android-phone.ip = "192.168.44.44" # Static IP of the Pwnagotchi
main.plugins.bt-tether.devices.android-phone.netmask = 24 # Netmask of the PAN
main.plugins.bt-tether.devices.android-phone.interval = 1 # Search interval in minutes
main.plugins.bt-tether.devices.android-phone.scantime = 10 # Duration of each search in seconds
main.plugins.bt-tether.devices.android-phone.max_tries = 10 # Maximum attempts to find the phone
main.plugins.bt-tether.devices.android-phone.share_internet = false # Enable internet sharing via Bluetooth
main.plugins.bt-tether.devices.android-phone.priority = 1 # Priority level for tethering
# Configuration for iOS Phone
main.plugins.bt-tether.devices.ios-phone.enabled = false
main.plugins.bt-tether.devices.ios-phone.search_order = 1
main.plugins.bt-tether.devices.ios-phone.mac = "" # Bluetooth MAC address of the iOS phone
main.plugins.bt-tether.devices.ios-phone.ip = "" # Static IP of the Pwnagotchi when tethered to iOS
main.plugins.bt-tether.devices.ios-phone.netmask = 24 # Netmask of the PAN
main.plugins.bt-tether.devices.ios-phone.interval = 1 # Search interval in minutes
main.plugins.bt-tether.devices.ios-phone.scantime = 10 # Duration of each search in seconds
main.plugins.bt-tether.devices.ios-phone.max_tries = 10 # Maximum attempts to find the phone
main.plugins.bt-tether.devices.ios-phone.share_internet = false # Enable internet sharing via Bluetooth
main.plugins.bt-tether.devices.ios-phone.priority = 1 # Priority level for tethering (edited)
main.plugins.fix_services.enabled = true main.plugins.fix_services.enabled = true
@ -42,8 +62,6 @@ main.plugins.gps.enabled = false
main.plugins.gps.speed = 19200 main.plugins.gps.speed = 19200
main.plugins.gps.device = "/dev/ttyUSB0" # for GPSD: "localhost:2947" main.plugins.gps.device = "/dev/ttyUSB0" # for GPSD: "localhost:2947"
main.plugins.gps_listener.enabled = false
main.plugins.grid.enabled = true main.plugins.grid.enabled = true
main.plugins.grid.report = true main.plugins.grid.report = true
@ -54,16 +72,19 @@ main.plugins.memtemp.enabled = false
main.plugins.memtemp.scale = "celsius" main.plugins.memtemp.scale = "celsius"
main.plugins.memtemp.orientation = "horizontal" 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.enabled = false
main.plugins.onlinehashcrack.email = "" main.plugins.onlinehashcrack.email = ""
main.plugins.onlinehashcrack.dashboard = "" main.plugins.onlinehashcrack.dashboard = ""
main.plugins.onlinehashcrack.single_files = false main.plugins.onlinehashcrack.single_files = false
main.plugins.pisugarx.enabled = false main.plugins.pisugar2.enabled = false
main.plugins.pisugarx.rotation = false main.plugins.pisugar2.shutdown = 5
main.plugins.pisugarx.default_display = "percentage" main.plugins.pisugar2.sync_rtc_on_boot = false
main.plugins.session-stats.enabled = false main.plugins.session-stats.enabled = true
main.plugins.session-stats.save_directory = "/var/tmp/pwnagotchi/sessions/" main.plugins.session-stats.save_directory = "/var/tmp/pwnagotchi/sessions/"
main.plugins.ups_hat_c.enabled = false main.plugins.ups_hat_c.enabled = false
@ -87,19 +108,32 @@ main.plugins.wpa-sec.enabled = false
main.plugins.wpa-sec.api_key = "" main.plugins.wpa-sec.api_key = ""
main.plugins.wpa-sec.api_url = "https://wpa-sec.stanev.org" main.plugins.wpa-sec.api_url = "https://wpa-sec.stanev.org"
main.plugins.wpa-sec.download_results = false main.plugins.wpa-sec.download_results = false
main.plugins.wpa-sec.show_pwd = false
main.iface = "wlan0mon" main.iface = "wlan0mon"
main.mon_start_cmd = "/usr/bin/monstart" main.mon_start_cmd = "/usr/bin/monstart"
main.mon_stop_cmd = "/usr/bin/monstop" main.mon_stop_cmd = "/usr/bin/monstop"
main.mon_max_blind_epochs = 5 main.mon_max_blind_epochs = 50
main.no_restart = false main.no_restart = false
main.filter = ""
main.log.path = "/etc/pwnagotchi/log/pwnagotchi.log" main.log.path = "/etc/pwnagotchi/log/pwnagotchi.log"
main.log.path-debug = "/etc/pwnagotchi/log/pwnagotchi-debug.log"
main.log.rotation.enabled = true main.log.rotation.enabled = true
main.log.rotation.size = "10M" 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.advertise = true
personality.deauth = true personality.deauth = true
personality.associate = true personality.associate = true
@ -121,8 +155,8 @@ personality.bond_encounters_factor = 20000
personality.throttle_a = 0.4 personality.throttle_a = 0.4
personality.throttle_d = 0.9 personality.throttle_d = 0.9
ui.invert = false # false = black background, true = white background personality.clear_on_exit = true # clear display when shutting down cleanly
ui.cursor = true
ui.fps = 0.0 ui.fps = 0.0
ui.font.name = "DejaVuSansMono" # for japanese: fonts-japanese-gothic ui.font.name = "DejaVuSansMono" # for japanese: fonts-japanese-gothic
ui.font.size_offset = 0 # will be added to the font size ui.font.size_offset = 0 # will be added to the font size
@ -168,7 +202,7 @@ ui.display.enabled = false
ui.display.rotation = 180 ui.display.rotation = 180
ui.display.type = "waveshare_4" ui.display.type = "waveshare_4"
bettercap.handshakes = "/home/pi/handshakes" bettercap.handshakes = "/root/handshakes"
bettercap.silence = [ bettercap.silence = [
"ble.device.new", "ble.device.new",
"ble.device.lost", "ble.device.lost",

View File

@ -3,8 +3,7 @@ import re
import tempfile import tempfile
import contextlib import contextlib
import shutil import shutil
#import _thread import _thread
import threading
import logging import logging
from time import sleep from time import sleep
@ -86,8 +85,7 @@ def setup_mounts(config):
if interval: if interval:
logging.debug("[FS] Starting thread to sync %s (interval: %d)", logging.debug("[FS] Starting thread to sync %s (interval: %d)",
options['mount'], interval) options['mount'], interval)
threading.Thread(target=m.daemonize, args=(interval,),name="File Sys", daemon=True).start() _thread.start_new_thread(m.daemonize, (interval,))
#_thread.start_new_thread(m.daemonize, (interval,))
else: else:
logging.debug("[FS] Not syncing %s, because interval is 0", logging.debug("[FS] Not syncing %s, because interval is 0",
options['mount']) options['mount'])

View File

@ -13,8 +13,7 @@ def is_connected():
try: try:
# check DNS # check DNS
host = 'https://api.opwngrid.xyz/api/v1/uptime' host = 'https://api.opwngrid.xyz/api/v1/uptime'
headers = {'user-agent': f'pwnagotchi/{pwnagotchi.__version__}'} r = requests.get(host, headers=None, timeout=(30.0, 60.0))
r = requests.get(host, headers=headers, timeout=(30.0, 60.0))
if r.json().get('isUp'): if r.json().get('isUp'):
return True return True
except: except:
@ -71,9 +70,10 @@ def update_data(last_session):
enabled = [name for name, options in pwnagotchi.config['main']['plugins'].items() if enabled = [name for name, options in pwnagotchi.config['main']['plugins'].items() if
'enabled' in options and options['enabled']] 'enabled' in options and options['enabled']]
language = pwnagotchi.config['main']['lang'] language = pwnagotchi.config['main']['lang']
ai = pwnagotchi.config['ai']['enabled']
data = { data = {
'ai': "No AI!", 'ai': ai,
'session': { 'session': {
'duration': last_session.duration, 'duration': last_session.duration,
'epochs': last_session.epochs, 'epochs': last_session.epochs,
@ -87,11 +87,12 @@ def update_data(last_session):
'peers': last_session.peers, 'peers': last_session.peers,
}, },
'uname': subprocess.getoutput("uname -a"), 'uname': subprocess.getoutput("uname -a"),
'brain': brain,
'version': pwnagotchi.__version__, 'version': pwnagotchi.__version__,
'build': "Pwnagotchi by Jayofelony", 'build': "Pwnagotchi by Jayofelony",
'plugins': enabled, 'plugins': enabled,
'language': language, 'language': language,
'bettercap': subprocess.getoutput("bettercap -version"), 'bettercap': subprocess.getoutput("bettercap -version").split(".\n\n")[1],
'opwngrid': subprocess.getoutput("pwngrid -version") 'opwngrid': subprocess.getoutput("pwngrid -version")
} }

View File

@ -50,6 +50,10 @@ class KeyPair(object):
with open(self.fingerprint_path, 'w+t') as fp: with open(self.fingerprint_path, 'w+t') as fp:
fp.write(self.fingerprint) fp.write(self.fingerprint)
# no exception, keys loaded correctly.
self._view.on_starting()
return
except Exception as e: except Exception as e:
# if we're here, loading the keys broke something ... # if we're here, loading the keys broke something ...
logging.exception("error loading keys, maybe corrupted, deleting and regenerating ...") logging.exception("error loading keys, maybe corrupted, deleting and regenerating ...")
@ -59,9 +63,6 @@ class KeyPair(object):
except: except:
pass pass
# no exception, keys loaded correctly.
self._view.on_starting()
return
def sign(self, message): def sign(self, message):
hasher = SHA256.new(message.encode("ascii")) hasher = SHA256.new(message.encode("ascii"))
signer = PKCS1_PSS.new(self.priv_key, saltLen=16) 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. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!" msgid "Hack the Planet!"
msgstr "" msgstr ""
msgid "No more mister Wi-Fi!!" msgid "AI ready."
msgstr "" msgstr ""
msgid "Pretty fly 4 a Wi-Fi!" msgid "The neural network is ready."
msgstr "" msgstr ""
msgid "Generating keys, do not turn off ..." 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. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!" msgid "Hack the Planet!"
msgstr "" msgstr ""
msgid "No more mister Wi-Fi!!" msgid "AI ready."
msgstr "" msgstr ""
msgid "Pretty fly 4 a Wi-Fi!" msgid "The neural network is ready."
msgstr "" msgstr ""
msgid "Generating keys, do not turn off ..." 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. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!" msgid "Hack the Planet!"
msgstr "" msgstr ""
msgid "No more mister Wi-Fi!!" msgid "AI ready."
msgstr "" msgstr ""
msgid "Pretty fly 4 a Wi-Fi!" msgid "The neural network is ready."
msgstr "" msgstr ""
msgid "Generating keys, do not turn off ..." msgid "Generating keys, do not turn off ..."

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 1.5.3\n" "Project-Id-Version: 1.5.3\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: MatthewNunu https://github.com/MatthewNunu\n" "Last-Translator: MatthewNunu https://github.com/MatthewNunu\n"
"Language-Team: \n" "Language-Team: \n"
@ -28,11 +28,11 @@ msgstr "Nuwe dag, nuwe jag, nuwe pwns!"
msgid "Hack the Planet!" msgid "Hack the Planet!"
msgstr "Hack die wêreld!" msgstr "Hack die wêreld!"
msgid "No more mister Wi-Fi!!" msgid "AI ready."
msgstr "" msgstr "AI gereed."
msgid "Pretty fly 4 a Wi-Fi!" msgid "The neural network is ready."
msgstr "" msgstr "Die neurale netwerk is gereed."
msgid "Generating keys, do not turn off ..." msgid "Generating keys, do not turn off ..."
msgstr "Genereer wagwoord, moenie afskakel nie ..." msgstr "Genereer wagwoord, moenie afskakel nie ..."
@ -257,9 +257,3 @@ msgstr "minuut"
msgid "second" msgid "second"
msgstr "tweede" 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. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!" msgid "Hack the Planet!"
msgstr "" msgstr ""
msgid "No more mister Wi-Fi!!" msgid "AI ready."
msgstr "" msgstr ""
msgid "Pretty fly 4 a Wi-Fi!" msgid "The neural network is ready."
msgstr "" msgstr ""
msgid "Generating keys, do not turn off ..." 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. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!" msgid "Hack the Planet!"
msgstr "" msgstr ""
msgid "No more mister Wi-Fi!!" msgid "AI ready."
msgstr "" msgstr ""
msgid "Pretty fly 4 a Wi-Fi!" msgid "The neural network is ready."
msgstr "" msgstr ""
msgid "Generating keys, do not turn off ..." 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. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!" msgid "Hack the Planet!"
msgstr "" msgstr ""
msgid "No more mister Wi-Fi!!" msgid "AI ready."
msgstr "" msgstr ""
msgid "Pretty fly 4 a Wi-Fi!" msgid "The neural network is ready."
msgstr "" msgstr ""
msgid "Generating keys, do not turn off ..." 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. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!" msgid "Hack the Planet!"
msgstr "" msgstr ""
msgid "No more mister Wi-Fi!!" msgid "AI ready."
msgstr "" msgstr ""
msgid "Pretty fly 4 a Wi-Fi!" msgid "The neural network is ready."
msgstr "" msgstr ""
msgid "Generating keys, do not turn off ..." 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. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!" msgid "Hack the Planet!"
msgstr "" msgstr ""
msgid "No more mister Wi-Fi!!" msgid "AI ready."
msgstr "" msgstr ""
msgid "Pretty fly 4 a Wi-Fi!" msgid "The neural network is ready."
msgstr "" msgstr ""
msgid "Generating keys, do not turn off ..." 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. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!" msgid "Hack the Planet!"
msgstr "" msgstr ""
msgid "No more mister Wi-Fi!!" msgid "AI ready."
msgstr "" msgstr ""
msgid "Pretty fly 4 a Wi-Fi!" msgid "The neural network is ready."
msgstr "" msgstr ""
msgid "Generating keys, do not turn off ..." 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. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!" msgid "Hack the Planet!"
msgstr "" msgstr ""
msgid "No more mister Wi-Fi!!" msgid "AI ready."
msgstr "" msgstr ""
msgid "Pretty fly 4 a Wi-Fi!" msgid "The neural network is ready."
msgstr "" msgstr ""
msgid "Generating keys, do not turn off ..." 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. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!" msgid "Hack the Planet!"
msgstr "" msgstr ""
msgid "No more mister Wi-Fi!!" msgid "AI ready."
msgstr "" msgstr ""
msgid "Pretty fly 4 a Wi-Fi!" msgid "The neural network is ready."
msgstr "" msgstr ""
msgid "Generating keys, do not turn off ..." 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. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!" msgid "Hack the Planet!"
msgstr "" msgstr ""
msgid "No more mister Wi-Fi!!" msgid "AI ready."
msgstr "" msgstr ""
msgid "Pretty fly 4 a Wi-Fi!" msgid "The neural network is ready."
msgstr "" msgstr ""
msgid "Generating keys, do not turn off ..." msgid "Generating keys, do not turn off ..."

View File

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

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 0.0.1\n" "Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2019-10-23 20:56+0200\n"
"Last-Translator: Georgi Koemdzhiev <https://github.com/georgikoemdzhiev>\n" "Last-Translator: Georgi Koemdzhiev <https://github.com/georgikoemdzhiev>\n"
"Language-Team: \n" "Language-Team: \n"
@ -28,11 +28,11 @@ msgstr "Нов ден, нов лов, нови pwns!"
msgid "Hack the Planet!" msgid "Hack the Planet!"
msgstr "Хакни планетата!" msgstr "Хакни планетата!"
msgid "No more mister Wi-Fi!!" msgid "AI ready."
msgstr "" msgstr "AI готов."
msgid "Pretty fly 4 a Wi-Fi!" msgid "The neural network is ready."
msgstr "" msgstr "Невронната мрежа е готова."
msgid "Generating keys, do not turn off ..." msgid "Generating keys, do not turn off ..."
msgstr "Генериране на ключове, не изключвай ..." msgstr "Генериране на ключове, не изключвай ..."
@ -258,9 +258,3 @@ msgstr "минута"
msgid "second" msgid "second"
msgstr "секунда" 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. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!" msgid "Hack the Planet!"
msgstr "" msgstr ""
msgid "No more mister Wi-Fi!!" msgid "AI ready."
msgstr "" msgstr ""
msgid "Pretty fly 4 a Wi-Fi!" msgid "The neural network is ready."
msgstr "" msgstr ""
msgid "Generating keys, do not turn off ..." 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. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -28,10 +29,10 @@ msgstr ""
msgid "Hack the Planet!" msgid "Hack the Planet!"
msgstr "" msgstr ""
msgid "No more mister Wi-Fi!!" msgid "AI ready."
msgstr "" msgstr ""
msgid "Pretty fly 4 a Wi-Fi!" msgid "The neural network is ready."
msgstr "" msgstr ""
msgid "Generating keys, do not turn off ..." msgid "Generating keys, do not turn off ..."

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