mirror of
https://github.com/jayofelony/pwnagotchi.git
synced 2025-07-01 18:37:27 -04:00
Compare commits
249 Commits
Author | SHA1 | Date | |
---|---|---|---|
111787544f | |||
efa5d8b197 | |||
9e5fb49d7c | |||
cbbd7d5a6d | |||
90c5818123 | |||
eb76ef4c54 | |||
59e42daeb5 | |||
5b7014c68e | |||
42cc3136ee | |||
2a243870f7 | |||
8f043ff5ef | |||
255bbdbc08 | |||
6f57b66cf4 | |||
a4c25e9996 | |||
9495d55296 | |||
69e7503d67 | |||
25cb1f2175 | |||
54b7ce5d0d | |||
1f9afd541d | |||
87eae76a58 | |||
6691257036 | |||
2149c5dbdf | |||
77af772b4b | |||
14e4fc6d47 | |||
5be8580a59 | |||
f7a599ab8f | |||
5f907b236a | |||
bc92613700 | |||
501ec9ca2b | |||
e5e0180f3c | |||
ea60808700 | |||
a34db250b5 | |||
d29aca15a9 | |||
8531b89771 | |||
59d510d0e1 | |||
913b1a6e1d | |||
de2cdaa3c9 | |||
f2cf34a8b9 | |||
bbb46128fe | |||
46713b6e73 | |||
aa2b09fb21 | |||
9125e43b20 | |||
7e4d926b14 | |||
6417ef5a78 | |||
46c03063fe | |||
d5384d5a81 | |||
e800c66e57 | |||
e3a404cb39 | |||
6cb6aaeb81 | |||
5761dac073 | |||
3ada0628e1 | |||
9fa772c36a | |||
a92e66137c | |||
a7e98cf166 | |||
9a1a264a9f | |||
8a0d482fe0 | |||
0cc320d31f | |||
d841d2b649 | |||
966126a986 | |||
d67935fa6a | |||
46d867ce7f | |||
2e16069850 | |||
6e3cbbdd39 | |||
a19bd6a181 | |||
6c03d95724 | |||
60a9da9acc | |||
25aba0d73c | |||
ecde496534 | |||
7ebd4dd7a0 | |||
35f1707436 | |||
60ced12ea5 | |||
6082a0c169 | |||
a72c1b1628 | |||
e37a0bdc62 | |||
262f8bf551 | |||
5311dd9ddc | |||
bd9c44b4a3 | |||
3b01aec872 | |||
3e571bff2d | |||
391941e64a | |||
3fff6182ed | |||
73e0a1fce7 | |||
9ba23d77d1 | |||
8be75627e9 | |||
c2a36aa678 | |||
fdf0a087f6 | |||
c6efa5df08 | |||
62327a711e | |||
bb460a9cc6 | |||
93b2322ab5 | |||
53a8af4711 | |||
ed5decffa0 | |||
147cbfaa07 | |||
ced9b4d5ee | |||
472c3165ed | |||
1578d13d98 | |||
0965e7eb3e | |||
b789c14f14 | |||
0855b44d59 | |||
55c6007d32 | |||
a7cf8a3383 | |||
faa48b2752 | |||
d20619340c | |||
43a07fe969 | |||
bcce22c164 | |||
0264b7a7db | |||
fba5dd0341 | |||
dd7760efdf | |||
f7cd0fb1fc | |||
611a3e7fb5 | |||
599c6211e4 | |||
eb48d29851 | |||
0999b95be0 | |||
ae351e5e9c | |||
fa58136c0e | |||
3d5185f2c1 | |||
b6bb7b9080 | |||
20715351af | |||
1d3c781d12 | |||
bd51b4330f | |||
f216a21132 | |||
5ae357c3dc | |||
ed9afe6856 | |||
25d932fa9b | |||
ebd25d50b0 | |||
27e784a5d7 | |||
c02efb5224 | |||
107d366c82 | |||
488968ba7f | |||
0dd8f72c95 | |||
0e274af5a0 | |||
8627c8800d | |||
53ed6f61c6 | |||
835523241b | |||
418dbf21e3 | |||
9a40567daa | |||
eab331686d | |||
e1d8001fb4 | |||
3cc172a526 | |||
41afe302d9 | |||
5bcca7e89b | |||
4a12436942 | |||
d8b6363d37 | |||
b3d8a44208 | |||
3fb91498cd | |||
7587f38c28 | |||
d7fefa78f0 | |||
1ef3a88679 | |||
291ef39563 | |||
16d80a09e6 | |||
4ffad03c91 | |||
0638832532 | |||
f895b06978 | |||
f410feb2bb | |||
1ffe6c4e3d | |||
dfbfd7f891 | |||
a50695aef2 | |||
829a6962d6 | |||
ce1315b85f | |||
6a2703cc27 | |||
502e856934 | |||
dffcbbf447 | |||
333804c3bb | |||
50b1ceed81 | |||
877b9fbba3 | |||
8724ca546d | |||
961d3a536a | |||
c882d0b67a | |||
12fd081ae0 | |||
e1be0f7674 | |||
396b30f780 | |||
e7d8d632a0 | |||
5a6ec7b4b8 | |||
92cd5d3fdb | |||
99caa7a973 | |||
a94e7eef02 | |||
1cefae55d1 | |||
c5ee1df855 | |||
d142840307 | |||
e558146e28 | |||
11a3330153 | |||
53b2dd8628 | |||
6381f9443b | |||
fa7e87b974 | |||
cca2ff2da4 | |||
78415b3137 | |||
34284aa1bc | |||
56ebb60662 | |||
84f6624844 | |||
3bcbb0ce9a | |||
e01e457992 | |||
1780859889 | |||
fdd98bb37a | |||
86991304a5 | |||
4f62759d6d | |||
7b7ba02aad | |||
7040be2d30 | |||
d0617ccfaf | |||
a0b5078b64 | |||
54c1ffd63c | |||
7530709d0c | |||
a9a6fd424b | |||
8c97301992 | |||
91eaa22188 | |||
31a4af4c21 | |||
dceeaff1fb | |||
c0241dc8df | |||
8f405f4ab2 | |||
bd79e71563 | |||
7f662585aa | |||
d23ff8d47a | |||
04435229fe | |||
42e236bafe | |||
5081b72695 | |||
652740f050 | |||
1b6b12bdf5 | |||
c610335a34 | |||
6c39ed97dd | |||
967f1663c6 | |||
ffe1e90ccd | |||
f2d2bcbfdf | |||
bfc0795fb8 | |||
1a55afd74a | |||
1594e7c129 | |||
a244e70a1c | |||
d35d5d6c3c | |||
7aacf9fb44 | |||
dde6fa4c2a | |||
7dfb2f2ff6 | |||
cf46ab3da9 | |||
6824e541a4 | |||
1c4e8fa750 | |||
a50ec4a65c | |||
5414fdf21d | |||
c9883bb4f9 | |||
045f5853bc | |||
6dfcaf05d5 | |||
e2f8119d2c | |||
d675b3d0dd | |||
30850b6530 | |||
1e9c9bae42 | |||
64241515ad | |||
c9f232dae2 | |||
4f97bcaa83 | |||
7d2a03c79f | |||
f89ba85f73 | |||
57f03f4359 | |||
9bc266f9ff | |||
b0db0285bc |
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@ -1,4 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
patreon: pwnagotchi_torch
|
||||
github: jayofelony
|
||||
|
72
.github/workflows/publish.yml
vendored
Normal file
72
.github/workflows/publish.yml
vendored
Normal 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 }}
|
21
.idea/deployment.xml
generated
21
.idea/deployment.xml
generated
@ -1,4 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="PublishConfigData" filePermissions="493" folderPermissions="493" remoteFilesAllowedToDisappearOnAutoupload="false" />
|
||||
<component name="PublishConfigData" serverName="pwnagotchi" filePermissions="493" folderPermissions="493" remoteFilesAllowedToDisappearOnAutoupload="false" confirmBeforeUploading="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>
|
||||
</project>
|
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@ -3,7 +3,7 @@
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.11 (pwnagotchi)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (pwnagotchi-torch-bookworm)" project-jdk-type="Python SDK" />
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10" project-jdk-type="Python SDK" />
|
||||
<component name="PythonCompatibilityInspectionAdvertiser">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
|
2
.idea/pwnagotchi.iml
generated
2
.idea/pwnagotchi.iml
generated
@ -4,7 +4,7 @@
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.10 (pwnagotchi-torch-bookworm)" jdkType="Python SDK" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.10" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PyDocumentationSettings">
|
||||
|
40
Makefile
40
Makefile
@ -1,4 +1,4 @@
|
||||
PACKER_VERSION := 1.10.0
|
||||
PACKER_VERSION := 1.10.1
|
||||
PWN_HOSTNAME := pwnagotchi
|
||||
PWN_VERSION := $(shell cut -d"'" -f2 < pwnagotchi/_version.py)
|
||||
|
||||
@ -25,7 +25,8 @@ ifneq (,$(UNSHARE))
|
||||
UNSHARE := $(UNSHARE) --uts
|
||||
endif
|
||||
|
||||
all: clean image clean
|
||||
# sudo apt-get install qemu-user-static qemu-utils
|
||||
all: clean packer image
|
||||
|
||||
update_langs:
|
||||
@for lang in pwnagotchi/locale/*/; do\
|
||||
@ -39,30 +40,21 @@ compile_langs:
|
||||
./scripts/language.sh compile $$(basename $$lang); \
|
||||
done
|
||||
|
||||
PACKER := ~/pwnagotchi/packer
|
||||
PACKER_URL := https://releases.hashicorp.com/packer/$(PACKER_VERSION)/packer_$(PACKER_VERSION)_linux_$(GOARCH).zip
|
||||
$(PACKER):
|
||||
mkdir -p $(@D)
|
||||
curl -L "$(PACKER_URL)" -o $(PACKER).zip
|
||||
unzip $(PACKER).zip -d $(@D)
|
||||
rm $(PACKER).zip
|
||||
chmod +x $@
|
||||
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
|
||||
|
||||
SDIST := dist/pwnagotchi-$(PWN_VERSION).tar.gz
|
||||
$(SDIST): setup.py pwnagotchi
|
||||
python3 setup.py sdist
|
||||
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
|
||||
|
||||
# Building the image requires packer, but don't rebuild the image just because packer updated.
|
||||
pwnagotchi: | $(PACKER)
|
||||
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
|
||||
|
||||
# If the packer or ansible files are updated, rebuild the image.
|
||||
pwnagotchi: $(SDIST) builder/pwnagotchi.json.pkr.hcl builder/raspberrypi64.yml $(shell find builder/data -type f)
|
||||
|
||||
cd builder && $(PACKER) init pwnagotchi.json.pkr.hcl && sudo $(UNSHARE) $(PACKER) build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" pwnagotchi.json.pkr.hcl
|
||||
|
||||
.PHONY: image
|
||||
image: pwnagotchi
|
||||
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 build dist pwnagotchi.egg-info
|
||||
- rm -f $(PACKER)
|
||||
- rm -rf /tmp/packer*
|
||||
|
@ -11,7 +11,7 @@ Select ‘Credentials’ from the left menu, click ‘Create Credentials’, sel
|
||||
|
||||
Now, the product name and consent screen need to be set -> click ‘Configure consent screen’ and follow the instructions. Once finished:
|
||||
|
||||
Select ‘Application type’ to be Web application.
|
||||
Select ‘Application type’ to be Desktop application.
|
||||
|
||||
Enter an appropriate name.
|
||||
|
||||
@ -19,7 +19,7 @@ Input http://localhost/ for ‘Authorized redirect URIs’.
|
||||
|
||||
Select the correct oauth scope:
|
||||
|
||||
- drive.file
|
||||
- drive
|
||||
- drive.install
|
||||
|
||||
Click ‘Create’.
|
||||
|
27
README.md
27
README.md
@ -1,26 +1,7 @@
|
||||
# Pwnagotchi-Torch
|
||||
<a href="https://github.com/jayofelony/pwnagotchi-bookworm/releases/latest"><img alt="Release" src="https://img.shields.io/github/release/jayofelony/pwnagotchi-bookworm.svg"></a><br/>
|
||||
**This fork of [Pwnagotchi](https://www.pwnagotchi.ai) is only for 64-bit Raspberry Pi's. Such as the 02W, 3(b+) and 4(b) ~~and the new Raspberry Pi 5~~!!.**
|
||||
|
||||
It seems the Pi 5 is unable to run in monitor mode, will keep you updated on this.
|
||||
|
||||
If you are using an older 32-bit version Raspberry Pi, ZeroWH, use this [fork](https://github.com/jayofelony/pwnagotchi-torch/releases/tag/v2.6.4) and make sure you download the `armhf` version.
|
||||
|
||||
---
|
||||
Download latest image file [here](https://github.com/jayofelony/pwnagotchi-bookworm/releases/tag/v2.7.3), and let it auto-update from here on out.
|
||||
|
||||
**Use RPi imager to flash, please don't flash a new user as this will mess with logs created.**
|
||||
|
||||
SSH credentials are `pi/raspberry`.
|
||||
|
||||
# Donations:
|
||||
I would like to thank
|
||||
- [findingmoist](https://github.com/findingmoist)
|
||||
- [kr4k0n](https://github.com/kr4k0n)
|
||||
|
||||
for donating!
|
||||
|
||||
[Pwnagotchi-Torch](https://www.patreon.com/pwnagotchi_torch)
|
||||
# Pwnagotchi
|
||||
This is the main source for all forks:
|
||||
- RPiZeroW (32bit)
|
||||
- RPiZero2W, RPi3, RPi4, RPi5 (64bit)
|
||||
|
||||
[GH Sponsor](https://github.com/sponsors/jayofelony)
|
||||
|
||||
|
258
bin/pwnagotchi
258
bin/pwnagotchi
@ -7,6 +7,7 @@ import sys
|
||||
import toml
|
||||
import requests
|
||||
import os
|
||||
import re
|
||||
|
||||
import pwnagotchi
|
||||
from pwnagotchi import utils
|
||||
@ -18,84 +19,85 @@ 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_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))
|
||||
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()
|
||||
|
||||
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 ^_^
|
||||
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 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
|
||||
@ -132,6 +134,8 @@ def pwnagotchi_cli():
|
||||
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,
|
||||
@ -156,30 +160,128 @@ def pwnagotchi_cli():
|
||||
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? [Y/N]\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? [Y/N]\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")
|
||||
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? [Y/N] ")
|
||||
if pwn_bluetooth.lower() in ('y', 'yes'):
|
||||
f.write("main.plugins.bt-tether.enabled = true\n\n")
|
||||
pwn_bluetooth_device = input("What device do you use? Android or iOS? ")
|
||||
if pwn_bluetooth_device.lower() == "android":
|
||||
f.write("main.plugins.bt-tether.devices.android-phone.enabled = true\n")
|
||||
pwn_bluetooth_mac = input("What is the bluetooth MAC of your device? ")
|
||||
if pwn_bluetooth_mac != "":
|
||||
f.write(f"main.plugins.bt-tether.devices.android-phone.mac = \"{pwn_bluetooth_mac}\"\n")
|
||||
elif pwn_bluetooth_device.lower() == "ios":
|
||||
f.write("main.plugins.bt-tether.devices.ios-phone.enabled = true\n")
|
||||
pwn_bluetooth_mac = input("What is the bluetooth MAC of your device? ")
|
||||
if pwn_bluetooth_mac != "":
|
||||
f.write(f"main.plugins.bt-tether.devices.ios-phone.mac = \"{pwn_bluetooth_mac}\"\n")
|
||||
# set up display settings
|
||||
pwn_display_enabled = input("Do you use a display? [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-L431\n")
|
||||
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? [Y/N]\n\n"
|
||||
"N = Black background\n"
|
||||
"Y = White background\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"
|
||||
"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 made @ https://github.com/sponsors/jayofelony \n\nBut only if you really want to!")
|
||||
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-bookworm/releases/latest")
|
||||
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)]"
|
||||
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")
|
||||
os.system("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'):
|
||||
elif user_input.lower() in ('n', 'no'): # using this elif for readability
|
||||
print("Okay, guess not!")
|
||||
else:
|
||||
print("Invalid input.")
|
||||
else:
|
||||
print("You are currently on the latest release, v%s." % pwnagotchi.__version__)
|
||||
sys.exit(0)
|
||||
@ -224,7 +326,3 @@ def pwnagotchi_cli():
|
||||
do_manual_mode(agent)
|
||||
else:
|
||||
do_auto_mode(agent)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pwnagotchi_cli()
|
||||
|
173
builder/combined.json.pkr.hcl
Normal file
173
builder/combined.json.pkr.hcl
Normal 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"
|
||||
}
|
||||
}
|
62
builder/data/32bit/etc/dhcpcd.conf
Normal file
62
builder/data/32bit/etc/dhcpcd.conf
Normal file
@ -0,0 +1,62 @@
|
||||
# A sample configuration for dhcpcd.
|
||||
# See dhcpcd.conf(5) for details.
|
||||
|
||||
# Allow users of this group to interact with dhcpcd via the control socket.
|
||||
#controlgroup wheel
|
||||
|
||||
# Inform the DHCP server of our hostname for DDNS.
|
||||
hostname
|
||||
|
||||
# Use the hardware address of the interface for the Client ID.
|
||||
clientid
|
||||
# or
|
||||
# Use the same DUID + IAID as set in DHCPv6 for DHCPv4 ClientID as per RFC4361.
|
||||
# Some non-RFC compliant DHCP servers do not reply with this set.
|
||||
# In this case, comment out duid and enable clientid above.
|
||||
#duid
|
||||
|
||||
# Persist interface configuration when dhcpcd exits.
|
||||
persistent
|
||||
|
||||
# Rapid commit support.
|
||||
# Safe to enable by default because it requires the equivalent option set
|
||||
# on the server to actually work.
|
||||
option rapid_commit
|
||||
|
||||
# A list of options to request from the DHCP server.
|
||||
option domain_name_servers, domain_name, domain_search, host_name
|
||||
option classless_static_routes
|
||||
# Respect the network MTU. This is applied to DHCP routes.
|
||||
option interface_mtu
|
||||
|
||||
# Most distributions have NTP support.
|
||||
#option ntp_servers
|
||||
|
||||
# A ServerID is required by RFC2131.
|
||||
require dhcp_server_identifier
|
||||
|
||||
# Generate SLAAC address using the Hardware Address of the interface
|
||||
#slaac hwaddr
|
||||
# OR generate Stable Private IPv6 Addresses based from the DUID
|
||||
slaac private
|
||||
|
||||
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
# !! DO NOT EDIT THESE LINES BELOW PLEASE !!
|
||||
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
# static IP configuration:
|
||||
denyinterfaces wlan0
|
||||
|
||||
interface eth0
|
||||
static domain_name_servers=8.8.8.8 1.1.1.1
|
||||
metric 201
|
||||
|
||||
interface usb0
|
||||
static ip_address=10.0.0.2/24
|
||||
static routers=10.0.0.1
|
||||
static domain_name_servers=10.0.0.1 8.8.8.8 1.1.1.1
|
||||
metric 202
|
||||
|
||||
interface bnep0
|
||||
static domain_name_servers=8.8.8.8 1.1.1.1
|
||||
metric 203
|
26
builder/data/32bit/etc/dphys-swapfile
Normal file
26
builder/data/32bit/etc/dphys-swapfile
Normal file
@ -0,0 +1,26 @@
|
||||
# /etc/dphys-swapfile - user settings for dphys-swapfile package
|
||||
# author Neil Franklin, last modification 2010.05.05
|
||||
# copyright ETH Zuerich Physics Departement
|
||||
# use under either modified/non-advertising BSD or GPL license
|
||||
|
||||
# this file is sourced with . so full normal sh syntax applies
|
||||
|
||||
# the default settings are added as commented out CONF_*=* lines
|
||||
|
||||
|
||||
# where we want the swapfile to be, this is the default
|
||||
#CONF_SWAPFILE=/var/swap
|
||||
|
||||
# set size to absolute value, leaving empty (default) then uses computed value
|
||||
# you most likely don't want this, unless you have an special disk situation
|
||||
CONF_SWAPSIZE=2048
|
||||
|
||||
# set size to computed value, this times RAM size, dynamically adapts,
|
||||
# guarantees that there is enough swap without wasting disk space on excess
|
||||
#CONF_SWAPFACTOR=2
|
||||
|
||||
# restrict size (computed and absolute!) to maximally this limit
|
||||
# can be set to empty for no limit, but beware of filled partitions!
|
||||
# this is/was a (outdated?) 32bit kernel limit (in MBytes), do not overrun it
|
||||
# but is also sensible on 64bit to prevent filling /var or even / partition
|
||||
#CONF_MAXSWAP=2048
|
20
builder/data/32bit/etc/systemd/system/bluetooth.service
Normal file
20
builder/data/32bit/etc/systemd/system/bluetooth.service
Normal file
@ -0,0 +1,20 @@
|
||||
[Unit]
|
||||
Description=Bluetooth service
|
||||
Documentation=man:bluetoothd(8)
|
||||
ConditionPathIsDirectory=/sys/class/bluetooth
|
||||
|
||||
[Service]
|
||||
Type=dbus
|
||||
BusName=org.bluez
|
||||
ExecStart=/usr/libexec/bluetooth/bluetoothd --noplugin=sap,a2dp
|
||||
NotifyAccess=main
|
||||
#WatchdogSec=10
|
||||
#Restart=on-failure
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
||||
LimitNPROC=1
|
||||
ProtectHome=true
|
||||
ProtectSystem=full
|
||||
|
||||
[Install]
|
||||
WantedBy=bluetooth.target
|
||||
Alias=dbus-org.bluez.service
|
@ -0,0 +1,6 @@
|
||||
[Unit]
|
||||
After=hciuart.service bluetooth.service
|
||||
Before=
|
||||
|
||||
[Service]
|
||||
ExecStartPre=/bin/sleep 5
|
@ -8,7 +8,7 @@ After=bettercap.service
|
||||
Environment=LD_PRELOAD=/usr/local/lib/libpcap.so.1
|
||||
Environment=LD_LIBRARY_PATH=/usr/local/lib
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/pwngrid -keys /etc/pwnagotchi -peers /root/peers -address 127.0.0.1:8666 -client-token /root/.api-enrollment.json -wait -log /home/pi/logs/pwngrid-peer.log -iface wlan0mon
|
||||
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
|
||||
|
40
builder/data/32bit/extras/nexmon.yml
Normal file
40
builder/data/32bit/extras/nexmon.yml
Normal file
@ -0,0 +1,40 @@
|
||||
# Install nexmon to fix wireless scanning (takes 2.5G of space)
|
||||
- name: clone nexmon repository
|
||||
git:
|
||||
repo: https://github.com/DrSchottky/nexmon.git
|
||||
dest: /usr/local/src/nexmon
|
||||
|
||||
- name: make firmware
|
||||
shell: "source ./setup_env.sh && make"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/nexmon/
|
||||
|
||||
- name: "make firmware patch ({{ item.name }})"
|
||||
shell: "source ./setup_env.sh && cd /usr/local/src/nexmon/patches/{{ item.patch }}/nexmon/ && make"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src/nexmon/
|
||||
environment:
|
||||
QEMU_UNAME: "{{ item.kernel }}"
|
||||
ARCHFLAGS: "{{ item.arch_flags }}"
|
||||
|
||||
- name: "install new firmware ({{ item.name }})"
|
||||
copy:
|
||||
src: "/usr/local/src/nexmon/patches/{{ item.patch }}/nexmon/{{ item.firmware }}"
|
||||
dest: "/usr/lib/firmware/brcm/{{ item.firmware }}"
|
||||
follow: true
|
||||
environment:
|
||||
QEMU_UNAME: "{{ item.kernel }}"
|
||||
ARCHFLAGS: "{{ item.arch_flags }}"
|
||||
|
||||
- name: backup original driver
|
||||
command: "mv /usr/lib/modules/{{ item.kernel }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz /usr/lib/modules/{{ item.kernel }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.xz.orig"
|
||||
|
||||
- name: copy modified driver
|
||||
copy:
|
||||
src: "/usr/local/src/nexmon/patches/driver/brcmfmac_6.1.y-nexmon/brcmfmac.ko"
|
||||
dest: "/usr/lib/modules/{{ item.kernel }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko"
|
||||
|
||||
- name : load brcmfmac drivers
|
||||
command: "/sbin/depmod -a {{ item.kernel }}"
|
94
builder/data/32bit/raspberrypi32.json.pkr.hcl
Normal file
94
builder/data/32bit/raspberrypi32.json.pkr.hcl
Normal file
@ -0,0 +1,94 @@
|
||||
packer {
|
||||
required_plugins {
|
||||
arm = {
|
||||
version = "1.0.0"
|
||||
source = "github.com/cdecoux/builder-arm"
|
||||
}
|
||||
ansible = {
|
||||
source = "github.com/hashicorp/ansible"
|
||||
version = ">= 1.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"
|
||||
}
|
||||
}
|
593
builder/data/32bit/raspberrypi32.yml
Normal file
593
builder/data/32bit/raspberrypi32.yml
Normal file
@ -0,0 +1,593 @@
|
||||
---
|
||||
- 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
|
||||
alias custom='cd /usr/local/share/pwnagotchi/custom-plugins/'
|
||||
alias config='sudo nano /etc/pwnagotchi/config.toml'
|
||||
alias pwnlog='tail -f -n300 /etc/pwnagotchi/log/pwn*.log | sed --unbuffered "s/,[[:digit:]]\\{3\\}\\]//g" | cut -d " " -f 2-'
|
||||
alias pwnver='python3 -c "import pwnagotchi as p; print(p.__version__)"'
|
||||
alias pwnkill='sudo killall -USR1 pwnagotchi'
|
||||
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_4"
|
||||
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 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
|
@ -18,7 +18,7 @@ if ! check_brcm; then
|
||||
sleep 10
|
||||
fi
|
||||
|
||||
# start mon0
|
||||
# start wlan0mon
|
||||
start_monitor_interface
|
||||
|
||||
if is_auto_mode_no_delete; then
|
149
builder/data/32bit/usr/bin/decryption-webserver
Executable file
149
builder/data/32bit/usr/bin/decryption-webserver
Executable file
@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from urllib.parse import parse_qsl
|
||||
|
||||
|
||||
_HTML_FORM_TEMPLATE = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Decryption</title>
|
||||
<style>
|
||||
body {{ text-align: center; padding: 150px; }}
|
||||
h1 {{ font-size: 50px; }}
|
||||
body {{ font: 20px Helvetica, sans-serif; color: #333; }}
|
||||
article {{ display: block; text-align: center; width: 650px; margin: 0 auto;}}
|
||||
input {{
|
||||
padding: 12px 20px;
|
||||
margin: 8px 0;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #ccc;
|
||||
}}
|
||||
input[type=password] {{
|
||||
width: 75%;
|
||||
font-size: 24px;
|
||||
}}
|
||||
input[type=submit] {{
|
||||
cursor: pointer;
|
||||
width: 75%;
|
||||
}}
|
||||
input[type=submit]:hover {{
|
||||
background-color: #d9d9d9;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<article>
|
||||
<h1>Decryption</h1>
|
||||
<p>Some of your files are encrypted.</p>
|
||||
<p>Please provide the decryption password.</p>
|
||||
<div>
|
||||
<form action="/set-password" method="POST">
|
||||
{password_fields}
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
POST_RESPONSE = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
/* Center the loader */
|
||||
#loader {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
z-index: 1;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
margin: -75px 0 0 -75px;
|
||||
border: 16px solid #f3f3f3;
|
||||
border-radius: 50%;
|
||||
border-top: 16px solid #3498db;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
-webkit-animation: spin 2s linear infinite;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
0% { -webkit-transform: rotate(0deg); }
|
||||
100% { -webkit-transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
#myDiv {
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
|
||||
function checkPwnagotchi() {
|
||||
var target = 'http://' + document.location.hostname + ':8080/';
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', target);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState == 4) {
|
||||
if (xhr.status == 200 || xhr.status == 401) {
|
||||
window.location.replace(target);
|
||||
}else{
|
||||
setTimeout(checkPwnagotchi, 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
setTimeout(checkPwnagotchi, 1000);
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body style="margin:0;">
|
||||
|
||||
<div id="loader"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
HTML_FORM = None
|
||||
|
||||
|
||||
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
|
||||
|
||||
def do_GET(self):
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(HTML_FORM.encode())
|
||||
|
||||
def do_POST(self):
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
body = self.rfile.read(content_length)
|
||||
for mapping, password in parse_qsl(body.decode('UTF-8')):
|
||||
with open('/tmp/.pwnagotchi-secret-{}'.format(mapping), 'wt') as pwfile:
|
||||
pwfile.write(password)
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(POST_RESPONSE.encode())
|
||||
|
||||
|
||||
with open('/root/.pwnagotchi-crypted') as crypted_file:
|
||||
mappings = [line.split()[0] for line in crypted_file.readlines()]
|
||||
fields = ''.join(['<label for="{m}">Passphrase for {m}:</label>\n<input type="password" id="{m}" name="{m}" value=""><br>'.format(m=m)
|
||||
for m in mappings])
|
||||
HTML_FORM = _HTML_FORM_TEMPLATE.format(password_fields=fields)
|
||||
|
||||
httpd = HTTPServer(('0.0.0.0', 80), SimpleHTTPRequestHandler)
|
||||
httpd.serve_forever()
|
184
builder/data/32bit/usr/bin/pwnlib
Executable file
184
builder/data/32bit/usr/bin/pwnlib
Executable file
@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# check if brcm is stuck
|
||||
check_brcm() {
|
||||
if [[ "$(journalctl -n10 -k --since -5m | grep -c 'brcmf_cfg80211_nexmon_set_channel.*Set Channel failed')" -ge 5 ]]; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# reload mod
|
||||
reload_brcm() {
|
||||
if ! modprobe -r brcmfmac; then
|
||||
return 1
|
||||
fi
|
||||
sleep 1
|
||||
if ! modprobe brcmfmac; then
|
||||
return 1
|
||||
fi
|
||||
sleep 2
|
||||
iw dev wlan0 set power_save off
|
||||
return 0
|
||||
}
|
||||
|
||||
# starts mon0
|
||||
start_monitor_interface() {
|
||||
rfkill unblock all
|
||||
ifconfig wlan0 up
|
||||
iw dev wlan0 set power_save off
|
||||
iw phy "$(iw phy | head -1 | cut -d" " -f2)" interface add wlan0mon type monitor
|
||||
rfkill unblock all
|
||||
ifconfig wlan0 down
|
||||
ifconfig wlan0mon up
|
||||
iw dev wlan0mon set power_save off
|
||||
}
|
||||
|
||||
# stops mon0
|
||||
stop_monitor_interface() {
|
||||
ifconfig wlan0mon down && iw dev wlan0mon del
|
||||
reload_brcm
|
||||
ifconfig wlan0 up
|
||||
}
|
||||
|
||||
# returns 0 if the specificed network interface is up
|
||||
is_interface_up() {
|
||||
if grep -qi 'up' /sys/class/net/"$1"/operstate; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# returns 0 if conditions for AUTO mode are met
|
||||
is_auto_mode() {
|
||||
# check override file first
|
||||
if [ -f /root/.pwnagotchi-manual ]; then
|
||||
# remove the override file if found
|
||||
rm -rf /root/.pwnagotchi-manual
|
||||
return 1
|
||||
fi
|
||||
|
||||
# check override file first
|
||||
if [ -f /root/.pwnagotchi-auto ]; then
|
||||
# remove the override file if found
|
||||
rm -rf /root/.pwnagotchi-auto
|
||||
return 0
|
||||
fi
|
||||
|
||||
# if usb0 is up, we're in MANU
|
||||
if is_interface_up usb0; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# if eth0 is up (for other boards), we're in MANU
|
||||
if is_interface_up eth0; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# no override, but none of the interfaces is up -> AUTO
|
||||
return 0
|
||||
}
|
||||
|
||||
# returns 0 if conditions for AUTO mode are met
|
||||
is_auto_mode_no_delete() {
|
||||
# check override file first
|
||||
if [ -f /root/.pwnagotchi-manual ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# check override file first
|
||||
if [ -f /root/.pwnagotchi-auto ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# if usb0 is up, we're in MANU
|
||||
if is_interface_up usb0; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# if eth0 is up (for other boards), we're in MANU
|
||||
if is_interface_up eth0; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# no override, but none of the interfaces is up -> AUTO
|
||||
return 0
|
||||
}
|
||||
|
||||
# check if we need to decrypt something
|
||||
is_crypted_mode() {
|
||||
if [ -f /root/.pwnagotchi-crypted ]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# decryption loop
|
||||
is_decrypted() {
|
||||
while read -r mapping container mount; do
|
||||
# mapping = name the device or file will be mapped to
|
||||
# container = the luks encrypted device or file
|
||||
# mount = the mountpoint
|
||||
|
||||
# fail if not mounted
|
||||
if ! mountpoint -q "$mount" >/dev/null 2>&1; then
|
||||
if [ -f /tmp/.pwnagotchi-secret-"$mapping" ]; then
|
||||
</tmp/.pwnagotchi-secret-"$mapping" read -r SECRET
|
||||
if ! test -b /dev/disk/by-id/dm-uuid-*"$(cryptsetup luksUUID "$container" | tr -d -)"*; then
|
||||
if echo -n "$SECRET" | cryptsetup luksOpen -d- "$container" "$mapping" >/dev/null 2>&1; then
|
||||
echo "Container decrypted!"
|
||||
fi
|
||||
fi
|
||||
|
||||
if mount /dev/mapper/"$mapping" "$mount" >/dev/null 2>&1; then
|
||||
echo "Mounted /dev/mapper/$mapping to $mount"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! ip -4 addr show wlan0 | grep inet >/dev/null 2>&1; then
|
||||
>/dev/null 2>&1 ip addr add 192.168.0.10/24 dev wlan0
|
||||
fi
|
||||
|
||||
if ! pgrep -f decryption-webserver >/dev/null 2>&1; then
|
||||
>/dev/null 2>&1 decryption-webserver &
|
||||
fi
|
||||
|
||||
if ! pgrep wpa_supplicant >/dev/null 2>&1; then
|
||||
>/tmp/wpa_supplicant.conf cat <<EOF
|
||||
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
|
||||
update_config=1
|
||||
ap_scan=2
|
||||
|
||||
network={
|
||||
ssid="DECRYPT-ME"
|
||||
mode=2
|
||||
key_mgmt=WPA-PSK
|
||||
psk="pwnagotchi"
|
||||
frequency=2437
|
||||
}
|
||||
EOF
|
||||
>/dev/null 2>&1 wpa_supplicant -u -s -O -D nl80211 -i wlan0 -c /tmp/wpa_supplicant.conf &
|
||||
fi
|
||||
|
||||
if ! pgrep dnsmasq >/dev/null 2>&1; then
|
||||
>/dev/null 2>&1 dnsmasq -k -p 53 -h -O "6,192.168.0.10" -A "/#/192.168.0.10" -i wlan0 -K -F 192.168.0.50,192.168.0.60,255.255.255.0,24h &
|
||||
fi
|
||||
|
||||
return 1
|
||||
fi
|
||||
done </root/.pwnagotchi-crypted
|
||||
|
||||
# overwrite passwords
|
||||
python3 -c 'print("A"*4096)' | tee /tmp/.pwnagotchi-secret-* >/dev/null
|
||||
# delete
|
||||
rm /tmp/.pwnagotchi-secret-*
|
||||
sync # flush
|
||||
|
||||
pkill wpa_supplicant
|
||||
pkill dnsmasq
|
||||
pid="$(pgrep -f "decryption-webserver")"
|
||||
[[ -n "$pid" ]] && kill "$pid"
|
||||
|
||||
return 0
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
_show_complete()
|
||||
{
|
||||
local cur opts node_names all_options opt_line
|
||||
all_options="
|
||||
pwnagotchi -h --help -C --config -U --user-config --manual --skip-session --clear --debug --version --print-config --wizard --check-update --donate {plugins,google}
|
||||
pwnagotchi plugins -h --help {list,install,enable,disable,uninstall,update,upgrade}
|
||||
pwnagotchi plugins list -i --installed -h --help
|
||||
pwnagotchi plugins install -h --help
|
||||
pwnagotchi plugins uninstall -h --help
|
||||
pwnagotchi plugins enable -h --help
|
||||
pwnagotchi plugins disable -h --help
|
||||
pwnagotchi plugins update -h --help
|
||||
pwnagotchi plugins upgrade -h --help
|
||||
pwnagotchi google -h --help {login,refresh}
|
||||
pwnagotchi google login -h --help
|
||||
pwnagotchi google refresh -h --help
|
||||
"
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
# shellcheck disable=SC2124
|
||||
cmd="${COMP_WORDS[@]:0:${#COMP_WORDS[@]}-1}"
|
||||
opt_line="$(grep -m1 "$cmd" <<<"$all_options")"
|
||||
if [[ ${cur} == -* ]] ; then
|
||||
opts="$(echo "$opt_line" | tr ' ' '\n' | awk '/^ *-/{gsub("[^a-zA-Z0-9-]","",$1);print $1}')"
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
opts="$(echo $opt_line | grep -Po '{\K[^}]+' | tr ',' '\n')"
|
||||
# shellcheck disable=SC2207
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
}
|
||||
|
||||
complete -F _show_complete pwnagotchi
|
26
builder/data/64bit/etc/dphys-swapfile
Normal file
26
builder/data/64bit/etc/dphys-swapfile
Normal file
@ -0,0 +1,26 @@
|
||||
# /etc/dphys-swapfile - user settings for dphys-swapfile package
|
||||
# author Neil Franklin, last modification 2010.05.05
|
||||
# copyright ETH Zuerich Physics Departement
|
||||
# use under either modified/non-advertising BSD or GPL license
|
||||
|
||||
# this file is sourced with . so full normal sh syntax applies
|
||||
|
||||
# the default settings are added as commented out CONF_*=* lines
|
||||
|
||||
|
||||
# where we want the swapfile to be, this is the default
|
||||
#CONF_SWAPFILE=/var/swap
|
||||
|
||||
# set size to absolute value, leaving empty (default) then uses computed value
|
||||
# you most likely don't want this, unless you have an special disk situation
|
||||
CONF_SWAPSIZE=2048
|
||||
|
||||
# set size to computed value, this times RAM size, dynamically adapts,
|
||||
# guarantees that there is enough swap without wasting disk space on excess
|
||||
#CONF_SWAPFACTOR=2
|
||||
|
||||
# restrict size (computed and absolute!) to maximally this limit
|
||||
# can be set to empty for no limit, but beware of filled partitions!
|
||||
# this is/was a (outdated?) 32bit kernel limit (in MBytes), do not overrun it
|
||||
# but is also sensible on 64bit to prevent filling /var or even / partition
|
||||
#CONF_MAXSWAP=2048
|
@ -5,4 +5,4 @@ iface usb0 inet static
|
||||
network 10.0.0.0
|
||||
broadcast 10.0.0.255
|
||||
gateway 10.0.0.1
|
||||
metric 101
|
||||
metric 101
|
13
builder/data/64bit/etc/systemd/system/bettercap.service
Normal file
13
builder/data/64bit/etc/systemd/system/bettercap.service
Normal file
@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=bettercap api.rest service.
|
||||
Documentation=https://bettercap.org
|
||||
Wants=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/bettercap-launcher
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
20
builder/data/64bit/etc/systemd/system/bluetooth.service
Normal file
20
builder/data/64bit/etc/systemd/system/bluetooth.service
Normal file
@ -0,0 +1,20 @@
|
||||
[Unit]
|
||||
Description=Bluetooth service
|
||||
Documentation=man:bluetoothd(8)
|
||||
ConditionPathIsDirectory=/sys/class/bluetooth
|
||||
|
||||
[Service]
|
||||
Type=dbus
|
||||
BusName=org.bluez
|
||||
ExecStart=/usr/libexec/bluetooth/bluetoothd --noplugin=sap,a2dp
|
||||
NotifyAccess=main
|
||||
#WatchdogSec=10
|
||||
#Restart=on-failure
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
||||
LimitNPROC=1
|
||||
ProtectHome=true
|
||||
ProtectSystem=full
|
||||
|
||||
[Install]
|
||||
WantedBy=bluetooth.target
|
||||
Alias=dbus-org.bluez.service
|
20
builder/data/64bit/etc/systemd/system/pwnagotchi.service
Normal file
20
builder/data/64bit/etc/systemd/system/pwnagotchi.service
Normal file
@ -0,0 +1,20 @@
|
||||
[Unit]
|
||||
Description=pwnagotchi Deep Reinforcement Learning instrumenting bettercap for WiFI pwning.
|
||||
Documentation=https://pwnagotchi.org
|
||||
Wants=network.target
|
||||
After=pwngrid-peer.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=~
|
||||
ExecStart=/usr/bin/pwnagotchi-launcher
|
||||
ExecStopPost=/usr/bin/bash -c "if egrep -qi 'personality.clear_on_exit[ =]*true' /etc/pwnagotchi/config.toml ; then /usr/local/bin/pwnagotchi --clear; fi"
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
TasksMax=infinity
|
||||
LimitNPROC=infinity
|
||||
StandardOutput=null
|
||||
StandardError=null
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
16
builder/data/64bit/etc/systemd/system/pwngrid-peer.service
Normal file
16
builder/data/64bit/etc/systemd/system/pwngrid-peer.service
Normal file
@ -0,0 +1,16 @@
|
||||
[Unit]
|
||||
Description=pwngrid peer service.
|
||||
Documentation=https://pwnagotchi.ai
|
||||
Wants=network.target
|
||||
After=bettercap.service
|
||||
|
||||
[Service]
|
||||
Environment=LD_PRELOAD=/usr/local/lib/libpcap.so.1
|
||||
Environment=LD_LIBRARY_PATH=/usr/local/lib
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/pwngrid -keys /etc/pwnagotchi -peers /root/peers -address 127.0.0.1:8666 -client-token /root/.api-enrollment.json -wait -log /etc/pwnagotchi/log/pwngrid-peer.log -iface wlan0mon
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
34
builder/data/64bit/etc/update-motd.d/01-motd
Executable file
34
builder/data/64bit/etc/update-motd.d/01-motd
Executable file
@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
_hostname=$(hostname)
|
||||
_version=$(cut -d"'" -f2 < /usr/local/lib/python3.11/dist-packages/pwnagotchi/_version.py)
|
||||
echo
|
||||
echo "(☉_☉ ) $_hostname"
|
||||
echo
|
||||
echo " Hi! I'm a pwnagotchi $_version, please take good care of me!"
|
||||
echo " Here are some basic things you need to know to raise me properly!"
|
||||
echo
|
||||
echo " If you want to change my configuration, use /etc/pwnagotchi/config.toml"
|
||||
echo " All plugin config files are located in /etc/pwnagotchi/conf.d/"
|
||||
echo " Read the readme if you want to use gdrivesync plugin!!"
|
||||
echo
|
||||
echo " All the configuration options can be found on /etc/pwnagotchi/default.toml,"
|
||||
echo " but don't change this file because I will recreate it every time I'm restarted!"
|
||||
echo
|
||||
echo " I use oPwnGrid as my main API, you can check stats at https://opwngrid.xyz"
|
||||
echo
|
||||
echo " I'm managed by systemd. Here are some basic commands."
|
||||
echo
|
||||
echo " If you want to know what I'm doing, you can check my logs with the command"
|
||||
echo " - pwnlog"
|
||||
echo " - sudo pwnagotchi --wizard, to help set up a config.toml"
|
||||
echo " - sudo pwnagotchi --version, to check the current version"
|
||||
echo " - sudo pwnagotchi --donate, to see how you can donate to this project"
|
||||
echo " - sudo pwnagotchi --check-update, to see if there is a new version available"
|
||||
echo
|
||||
echo " If you want to know if I'm running, you can use"
|
||||
echo " sudo systemctl status pwnagotchi"
|
||||
echo
|
||||
echo " You can restart me using"
|
||||
echo " pwnkill"
|
||||
echo
|
||||
echo " You can learn more about me at https://pwnagotchi.org/"
|
@ -1,14 +1,12 @@
|
||||
# This is not working quite yet
|
||||
# https://github.com/mkaczanowski/packer-builder-arm/pull/172
|
||||
packer {
|
||||
required_plugins {
|
||||
#arm = {
|
||||
# version = "~> 1"
|
||||
# source = "github.com/cdecoux/builder-arm"
|
||||
#}
|
||||
arm = {
|
||||
version = "1.0.0"
|
||||
source = "github.com/cdecoux/builder-arm"
|
||||
}
|
||||
ansible = {
|
||||
source = "github.com/hashicorp/ansible"
|
||||
version = "~> 1"
|
||||
version = ">= 1.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -27,9 +25,9 @@ source "arm" "rpi64-pwnagotchi" {
|
||||
file_checksum_type = "sha256"
|
||||
file_target_extension = "xz"
|
||||
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
|
||||
image_path = "../../../pwnagotchi-rpi-bookworm-${var.pwn_version}-arm64.img"
|
||||
qemu_binary_source_path = "/usr/bin/qemu-aarch64-static"
|
||||
qemu_binary_destination_path = "/usr/bin/qemu-aarch64-static"
|
||||
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"
|
||||
@ -51,6 +49,8 @@ source "arm" "rpi64-pwnagotchi" {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
# 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
|
||||
@ -61,13 +61,13 @@ build {
|
||||
provisioner "file" {
|
||||
destination = "/usr/bin/"
|
||||
sources = [
|
||||
"data/usr/bin/bettercap-launcher",
|
||||
"data/usr/bin/hdmioff",
|
||||
"data/usr/bin/hdmion",
|
||||
"data/usr/bin/monstart",
|
||||
"data/usr/bin/monstop",
|
||||
"data/usr/bin/pwnagotchi-launcher",
|
||||
"data/usr/bin/pwnlib",
|
||||
"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" {
|
||||
@ -77,15 +77,14 @@ build {
|
||||
provisioner "file" {
|
||||
destination = "/etc/systemd/system/"
|
||||
sources = [
|
||||
"data/etc/systemd/system/bettercap.service",
|
||||
"data/etc/systemd/system/pwnagotchi.service",
|
||||
"data/etc/systemd/system/pwngrid-peer.service",
|
||||
"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/etc/update-motd.d/01-motd"
|
||||
source = "data/64bit/etc/update-motd.d/01-motd"
|
||||
}
|
||||
provisioner "shell" {
|
||||
inline = ["chmod +x /etc/update-motd.d/*"]
|
||||
@ -96,6 +95,6 @@ build {
|
||||
provisioner "ansible-local" {
|
||||
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook"
|
||||
extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
|
||||
playbook_file = "raspberrypi64.yml"
|
||||
playbook_file = "data/64bit/raspberrypi64.yml"
|
||||
}
|
||||
}
|
@ -6,10 +6,11 @@
|
||||
vars:
|
||||
kernel:
|
||||
min: "6.1"
|
||||
full: "6.1.0-rpi7-rpi-v8"
|
||||
full: "6.1.0-rpi8-rpi-v8"
|
||||
full_pi5: "6.1.0-rpi8-rpi-2712"
|
||||
pwnagotchi:
|
||||
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
|
||||
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi-torch', true) }}"
|
||||
version: "{{ lookup('env', 'PWN_VERSION') | default('pwnagotchi', true) }}"
|
||||
system:
|
||||
boot_options:
|
||||
- "dtoverlay=dwc2"
|
||||
@ -26,7 +27,6 @@
|
||||
- fstrim.timer
|
||||
- pwnagotchi.service
|
||||
- pwngrid-peer.service
|
||||
- zramswap.service
|
||||
disable:
|
||||
- apt-daily-upgrade.service
|
||||
- apt-daily-upgrade.timer
|
||||
@ -39,7 +39,7 @@
|
||||
source: "https://github.com/jayofelony/caplets.git"
|
||||
bettercap:
|
||||
source: "https://github.com/jayofelony/bettercap.git"
|
||||
url: "https://github.com/jayofelony/bettercap/releases/download/2.32.2/bettercap-2.32.2.zip"
|
||||
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"
|
||||
@ -51,13 +51,20 @@
|
||||
- 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-dev
|
||||
- libpcap0.8-dbg
|
||||
- libpcap0.8-dev
|
||||
remove:
|
||||
- avahi-daemon
|
||||
- dhpys-swapfile
|
||||
- libcurl-ocaml-dev
|
||||
- libssl-ocaml-dev
|
||||
- nfs-common
|
||||
- triggerhappy
|
||||
- wpasupplicant
|
||||
@ -70,7 +77,13 @@
|
||||
- build-essential
|
||||
- curl
|
||||
- dkms
|
||||
- dphys-swapfile
|
||||
- fbi
|
||||
- firmware-atheros
|
||||
- firmware-brcm80211
|
||||
- firmware-libertas
|
||||
- firmware-misc-nonfree
|
||||
- firmware-realtek
|
||||
- flex
|
||||
- fonts-dejavu
|
||||
- fonts-dejavu-core
|
||||
@ -80,6 +93,7 @@
|
||||
- gawk
|
||||
- gcc-arm-none-eabi
|
||||
- git
|
||||
- hcxtools
|
||||
- libatlas-base-dev
|
||||
- libavcodec59
|
||||
- libavformat59
|
||||
@ -89,6 +103,7 @@
|
||||
- libc-ares-dev
|
||||
- libc6-dev
|
||||
- libcap-dev
|
||||
- libcurl-ocaml-dev
|
||||
- libdbus-1-dev
|
||||
- libdbus-glib-1-dev
|
||||
- libeigen3-dev
|
||||
@ -115,34 +130,30 @@
|
||||
- libraspberrypi0
|
||||
- libsqlite3-dev
|
||||
- libssl-dev
|
||||
- libssl-ocaml-dev
|
||||
- libswscale5
|
||||
- libtiff6
|
||||
- libtool
|
||||
- libusb-1.0-0-dev
|
||||
- lsof
|
||||
- make
|
||||
- python3-yaml
|
||||
- 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
|
||||
- firmware-atheros
|
||||
- firmware-brcm80211
|
||||
- firmware-libertas
|
||||
- firmware-misc-nonfree
|
||||
- firmware-realtek
|
||||
- python3-pip
|
||||
- python3-setuptools
|
||||
- python3-smbus
|
||||
- python3-yaml
|
||||
- qpdf
|
||||
- raspberrypi-kernel-headers
|
||||
- rsync
|
||||
@ -157,13 +168,11 @@
|
||||
- wl
|
||||
- xxd
|
||||
- zlib1g-dev
|
||||
- zram-tools
|
||||
environment:
|
||||
ARCHFLAGS: "-arch aarch64"
|
||||
QEMU_UNAME: "{{ kernel.full }}"
|
||||
|
||||
|
||||
tasks:
|
||||
# First we install and remove unnecessary packages
|
||||
# First we install packages
|
||||
- name: install packages
|
||||
apt:
|
||||
name: "{{ packages.apt.install }}"
|
||||
@ -171,12 +180,11 @@
|
||||
update_cache: yes
|
||||
install_recommends: false
|
||||
|
||||
- name: remove unnecessary apt packages
|
||||
apt:
|
||||
name: "{{ packages.apt.remove }}"
|
||||
state: absent
|
||||
purge: yes
|
||||
register: removed
|
||||
- name: update pip3, setuptools, wheel
|
||||
shell: "python3 -m pip install --upgrade pip setuptools wheel --break-system-packages"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
chdir: /usr/local/src
|
||||
|
||||
# Now we set up /boot/firmware
|
||||
- name: Create pi user
|
||||
@ -271,35 +279,116 @@
|
||||
dest: /usr/local/lib/libpcap.so.0.8
|
||||
state: link
|
||||
|
||||
# Install nexmon to fix wireless scanning (takes 2.5G of space)
|
||||
# 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
|
||||
|
||||
- name: make firmware
|
||||
# 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)
|
||||
- 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: install new firmware (bcm43455c0)
|
||||
- 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:
|
||||
@ -312,6 +401,17 @@
|
||||
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:
|
||||
@ -342,13 +442,10 @@
|
||||
- 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: copy modified driver
|
||||
copy:
|
||||
src: "/usr/local/src/nexmon/patches/driver/brcmfmac_{{ kernel.min }}.y-nexmon/brcmfmac.ko"
|
||||
dest: "/usr/lib/modules/{{ kernel.full }}/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko"
|
||||
|
||||
- name : load brcmfmac drivers
|
||||
command: "/sbin/depmod -a"
|
||||
- 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
|
||||
@ -368,7 +465,7 @@
|
||||
|
||||
- name: clone pwnagotchi repository
|
||||
git:
|
||||
repo: https://github.com/jayofelony/pwnagotchi-bookworm.git
|
||||
repo: https://github.com/jayofelony/pwnagotchi.git
|
||||
dest: /usr/local/src/pwnagotchi
|
||||
|
||||
- name: build pwnagotchi wheel
|
||||
@ -400,6 +497,11 @@
|
||||
block: |
|
||||
export GOPATH=$HOME/go
|
||||
export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin
|
||||
alias custom='cd /usr/local/share/pwnagotchi/custom-plugins/'
|
||||
alias config='sudo nano /etc/pwnagotchi/config.toml'
|
||||
alias pwnlog='tail -f -n300 /etc/pwnagotchi/log/pwn*.log | sed --unbuffered "s/,[[:digit:]]\\{3\\}\\]//g" | cut -d " " -f 2-'
|
||||
alias pwnver='python3 -c "import pwnagotchi as p; print(p.__version__)"'
|
||||
alias pwnkill='sudo killall -USR1 pwnagotchi'
|
||||
when: golang.changed
|
||||
|
||||
- name: download pwngrid
|
||||
@ -423,7 +525,7 @@
|
||||
repo: "{{ packages.bettercap.source }}"
|
||||
dest: /usr/local/src/bettercap
|
||||
|
||||
- name: install bettercap 2.32.2
|
||||
- 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
|
||||
@ -476,11 +578,6 @@
|
||||
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
|
||||
@ -493,7 +590,7 @@
|
||||
# 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"
|
||||
# ui.display.type = "waveshare_4"
|
||||
when: not user_config.stat.exists
|
||||
|
||||
- name: Delete motd
|
||||
@ -506,24 +603,6 @@
|
||||
state: absent
|
||||
path: /etc/update-motd.d/10-uname
|
||||
|
||||
- name: Add pwnlog alias
|
||||
lineinfile:
|
||||
dest: /home/pi/.bashrc
|
||||
line: "\nalias pwnlog='tail -f -n300 /home/pi/logs/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 }}"
|
||||
@ -557,11 +636,6 @@
|
||||
group: pi
|
||||
recurse: true
|
||||
|
||||
- name: clean apt cache
|
||||
apt:
|
||||
autoclean: true
|
||||
when: removed.changed
|
||||
|
||||
- name: remove pre-collected packages zip
|
||||
file:
|
||||
path: /root/go_pkgs.tgz
|
||||
@ -582,11 +656,6 @@
|
||||
state: absent
|
||||
path: /root/.cache/pip
|
||||
|
||||
- name: remove dependencies that are no longer required
|
||||
apt:
|
||||
autoremove: yes
|
||||
when: removed.changed
|
||||
|
||||
- name: remove ssh keys
|
||||
file:
|
||||
state: absent
|
||||
@ -599,6 +668,24 @@
|
||||
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:
|
0
builder/data/64bit/root/client_secrets.json
Normal file
0
builder/data/64bit/root/client_secrets.json
Normal file
15
builder/data/64bit/root/settings.yaml
Normal file
15
builder/data/64bit/root/settings.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
client_config_backend: file
|
||||
client_config_file: /root/client_secrets.json
|
||||
client_config:
|
||||
client_id: <YOUR CLIENT ID>
|
||||
client_secret: <YOUR CLIENT SECRET>
|
||||
|
||||
save_credentials: True
|
||||
save_credentials_backend: file
|
||||
save_credentials_file: /root/credentials.json
|
||||
|
||||
get_refresh_token: True
|
||||
|
||||
oauth_scope:
|
||||
- https://www.googleapis.com/auth/drive
|
||||
- https://www.googleapis.com/auth/drive.install
|
19
builder/data/64bit/usr/bin/bettercap-launcher
Executable file
19
builder/data/64bit/usr/bin/bettercap-launcher
Executable file
@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
source /usr/bin/pwnlib
|
||||
|
||||
# we need to decrypt something
|
||||
if is_crypted_mode; then
|
||||
while ! is_decrypted; do
|
||||
echo "Waiting for decryption..."
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
|
||||
# start mon0
|
||||
start_monitor_interface
|
||||
|
||||
if is_auto_mode_no_delete; then
|
||||
/usr/local/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface wlan0mon
|
||||
else
|
||||
/usr/local/bin/bettercap -no-colors -caplet pwnagotchi-manual -iface wlan0mon
|
||||
fi
|
2
builder/data/64bit/usr/bin/hdmioff
Executable file
2
builder/data/64bit/usr/bin/hdmioff
Executable file
@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
sudo /usr/bin/tvservice -o
|
2
builder/data/64bit/usr/bin/hdmion
Executable file
2
builder/data/64bit/usr/bin/hdmion
Executable file
@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
sudo /usr/bin/tvservice -p
|
3
builder/data/64bit/usr/bin/monstart
Executable file
3
builder/data/64bit/usr/bin/monstart
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
source /usr/bin/pwnlib
|
||||
start_monitor_interface
|
3
builder/data/64bit/usr/bin/monstop
Executable file
3
builder/data/64bit/usr/bin/monstop
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
source /usr/bin/pwnlib
|
||||
stop_monitor_interface
|
16
builder/data/64bit/usr/bin/pwnagotchi-launcher
Executable file
16
builder/data/64bit/usr/bin/pwnagotchi-launcher
Executable file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
source /usr/bin/pwnlib
|
||||
|
||||
# we need to decrypt something
|
||||
if is_crypted_mode; then
|
||||
while ! is_decrypted; do
|
||||
echo "Waiting for decryption..."
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
|
||||
if is_auto_mode; then
|
||||
/usr/local/bin/pwnagotchi
|
||||
else
|
||||
/usr/local/bin/pwnagotchi --manual
|
||||
fi
|
@ -13,14 +13,6 @@ blink_led() {
|
||||
sleep 0.3
|
||||
}
|
||||
|
||||
# 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
|
@ -1,15 +0,0 @@
|
||||
# Specifies amount of zram devices to create.
|
||||
# By default, zramswap-start will use all available cores.
|
||||
#CORES=1
|
||||
|
||||
# Specifies the amount of RAM that should be used for zram
|
||||
# based on a percentage the total amount of available memory
|
||||
PERCENTAGE=60
|
||||
|
||||
# Specifies a static amount of RAM that should be used for
|
||||
# the ZRAM devices, this is in MiB
|
||||
#ALLOCATION=256
|
||||
|
||||
# Specifies the priority for the swap devices, see swapon(2)
|
||||
# for more details.
|
||||
#PRIORITY=100
|
@ -1 +1 @@
|
||||
__version__ = '2.7.6'
|
||||
__version__ = '2.8.6'
|
||||
|
@ -21,17 +21,17 @@ RECOVERY_DATA_FILE = '/root/.pwnagotchi-recovery'
|
||||
|
||||
class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
def __init__(self, view, config, keypair):
|
||||
Client.__init__(self, config['bettercap']['hostname'],
|
||||
config['bettercap']['scheme'],
|
||||
config['bettercap']['port'],
|
||||
config['bettercap']['username'],
|
||||
config['bettercap']['password'])
|
||||
Client.__init__(self,
|
||||
"127.0.0.1" if "hostname" not in config['bettercap'] else config['bettercap']['hostname'],
|
||||
"http" if "scheme" not in config['bettercap'] else config['bettercap']['scheme'],
|
||||
8081 if "port" not in config['bettercap'] else config['bettercap']['port'],
|
||||
"pwnagotchi" if "username" not in config['bettercap'] else config['bettercap']['username'],
|
||||
"pwnagotchi" if "password" not in config['bettercap'] else config['bettercap']['password'])
|
||||
Automata.__init__(self, config, view)
|
||||
AsyncAdvertiser.__init__(self, config, view, keypair)
|
||||
AsyncTrainer.__init__(self, config)
|
||||
|
||||
self._started_at = time.time()
|
||||
self._filter = None if not config['main']['filter'] else re.compile(config['main']['filter'])
|
||||
self._current_channel = 0
|
||||
self._tot_aps = 0
|
||||
self._aps_on_channel = 0
|
||||
@ -164,11 +164,6 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
|
||||
self.wait_for(recon_time, sleeping=False)
|
||||
|
||||
def _filter_included(self, ap):
|
||||
return self._filter is None or \
|
||||
self._filter.match(ap['hostname']) is not None or \
|
||||
self._filter.match(ap['mac']) is not None
|
||||
|
||||
def set_access_points(self, aps):
|
||||
self._access_points = aps
|
||||
plugins.on('wifi_update', self, aps)
|
||||
@ -184,13 +179,10 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
for ap in s['wifi']['aps']:
|
||||
if ap['encryption'] == '' or ap['encryption'] == 'OPEN':
|
||||
continue
|
||||
elif ap['hostname'] in whitelist or ap['mac'][:8].lower() in whitelist:
|
||||
elif ap['hostname'] in whitelist or ap['mac'][:13].lower() in whitelist or ap['mac'].lower() in whitelist:
|
||||
continue
|
||||
elif ap['hostname'] not in whitelist \
|
||||
and ap['mac'].lower() not in whitelist \
|
||||
and ap['mac'][:8].lower() not in whitelist:
|
||||
if self._filter_included(ap):
|
||||
aps.append(ap)
|
||||
else:
|
||||
aps.append(ap)
|
||||
except Exception as e:
|
||||
logging.exception("Error while getting access points (%s)", e)
|
||||
|
||||
@ -277,6 +269,10 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
self._save_recovery_data()
|
||||
pwnagotchi.reboot()
|
||||
|
||||
def _restart(self):
|
||||
self._save_recovery_data()
|
||||
pwnagotchi.restart("AUTO")
|
||||
|
||||
def _save_recovery_data(self):
|
||||
logging.warning("writing recovery data to %s ...", RECOVERY_DATA_FILE)
|
||||
with open(RECOVERY_DATA_FILE, 'w') as fp:
|
||||
@ -429,7 +425,6 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
if self.is_stale():
|
||||
logging.debug("recon is stale, skipping assoc(%s)", ap['mac'])
|
||||
return
|
||||
|
||||
if throttle == -1 and "throttle_a" in self._config['personality']:
|
||||
throttle = self._config['personality']['throttle_a']
|
||||
|
||||
|
@ -23,6 +23,12 @@ def load(config, agent, epoch, from_disk=True):
|
||||
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))
|
||||
|
@ -138,6 +138,6 @@ class Automata(object):
|
||||
plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data())
|
||||
|
||||
if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']:
|
||||
logging.critical("%d epochs without visible access points -> rebooting ...", self._epoch.blind_for)
|
||||
self._reboot()
|
||||
logging.critical("%d epochs without visible access points -> restarting ...", self._epoch.blind_for)
|
||||
self._restart()
|
||||
self._epoch.blind_for = 0
|
||||
|
@ -1,5 +1,11 @@
|
||||
main.name = "pwnagotchi"
|
||||
main.lang = "en"
|
||||
main.whitelist = [
|
||||
"EXAMPLE_NETWORK",
|
||||
"ANOTHER_EXAMPLE_NETWORK",
|
||||
"fo:od:ba:be:fo:od",
|
||||
"fo:od:ba"
|
||||
]
|
||||
main.confd = "/etc/pwnagotchi/conf.d/"
|
||||
main.custom_plugin_repos = [
|
||||
"https://github.com/jayofelony/pwnagotchi-torch-plugins/archive/master.zip",
|
||||
@ -26,7 +32,7 @@ main.plugins.bt-tether.devices.android-phone.netmask = 24
|
||||
main.plugins.bt-tether.devices.android-phone.interval = 1
|
||||
main.plugins.bt-tether.devices.android-phone.scantime = 10
|
||||
main.plugins.bt-tether.devices.android-phone.max_tries = 10
|
||||
main.plugins.bt-tether.devices.android-phone.share_internet = false
|
||||
main.plugins.bt-tether.devices.android-phone.share_internet = true
|
||||
main.plugins.bt-tether.devices.android-phone.priority = 1
|
||||
|
||||
main.plugins.bt-tether.devices.ios-phone.enabled = false
|
||||
@ -37,7 +43,7 @@ main.plugins.bt-tether.devices.ios-phone.netmask = 24
|
||||
main.plugins.bt-tether.devices.ios-phone.interval = 5
|
||||
main.plugins.bt-tether.devices.ios-phone.scantime = 20
|
||||
main.plugins.bt-tether.devices.ios-phone.max_tries = 0
|
||||
main.plugins.bt-tether.devices.ios-phone.share_internet = false
|
||||
main.plugins.bt-tether.devices.ios-phone.share_internet = true
|
||||
main.plugins.bt-tether.devices.ios-phone.priority = 999
|
||||
|
||||
main.plugins.fix_services.enabled = true
|
||||
@ -55,9 +61,6 @@ main.plugins.gps.device = "/dev/ttyUSB0" # for GPSD: "localhost:2947"
|
||||
|
||||
main.plugins.grid.enabled = true
|
||||
main.plugins.grid.report = true
|
||||
main.plugins.grid.exclude = [
|
||||
"YourHomeNetworkHere"
|
||||
]
|
||||
|
||||
main.plugins.logtail.enabled = false
|
||||
main.plugins.logtail.max-lines = 10000
|
||||
@ -73,10 +76,6 @@ main.plugins.onlinehashcrack.enabled = false
|
||||
main.plugins.onlinehashcrack.email = ""
|
||||
main.plugins.onlinehashcrack.dashboard = ""
|
||||
main.plugins.onlinehashcrack.single_files = false
|
||||
main.plugins.onlinehashcrack.whitelist = []
|
||||
|
||||
main.plugins.paw-gps.enabled = false
|
||||
main.plugins.paw-gps.ip = "192.168.44.1:8080"
|
||||
|
||||
main.plugins.pisugar2.enabled = false
|
||||
main.plugins.pisugar2.shutdown = 5
|
||||
@ -100,29 +99,22 @@ main.plugins.webgpsmap.enabled = false
|
||||
|
||||
main.plugins.wigle.enabled = false
|
||||
main.plugins.wigle.api_key = ""
|
||||
main.plugins.wigle.whitelist = []
|
||||
main.plugins.wigle.donate = true
|
||||
main.plugins.wigle.donate = false
|
||||
|
||||
main.plugins.wpa-sec.enabled = false
|
||||
main.plugins.wpa-sec.api_key = ""
|
||||
main.plugins.wpa-sec.api_url = "https://wpa-sec.stanev.org"
|
||||
main.plugins.wpa-sec.download_results = false
|
||||
main.plugins.wpa-sec.whitelist = []
|
||||
|
||||
main.iface = "wlan0mon"
|
||||
main.mon_start_cmd = "/usr/bin/monstart"
|
||||
main.mon_stop_cmd = "/usr/bin/monstop"
|
||||
main.mon_max_blind_epochs = 50
|
||||
main.no_restart = false
|
||||
main.whitelist = [
|
||||
"EXAMPLE_NETWORK",
|
||||
"ANOTHER_EXAMPLE_NETWORK",
|
||||
"fo:od:ba:be:fo:od",
|
||||
"fo:od:ba"
|
||||
]
|
||||
|
||||
main.filter = ""
|
||||
|
||||
main.log.path = "/home/pi/logs/pwnagotchi.log"
|
||||
main.log.path = "/etc/pwnagotchi/log/pwnagotchi.log"
|
||||
main.log.rotation.enabled = true
|
||||
main.log.rotation.size = "10M"
|
||||
|
||||
@ -160,6 +152,10 @@ personality.bond_encounters_factor = 20000
|
||||
personality.throttle_a = 0.4
|
||||
personality.throttle_d = 0.9
|
||||
|
||||
personality.clear_on_exit = true # clear display when shutting down cleanly
|
||||
|
||||
ui.invert = false # false = black background, true = white background
|
||||
|
||||
ui.fps = 0.0
|
||||
ui.font.name = "DejaVuSansMono" # for japanese: fonts-japanese-gothic
|
||||
ui.font.size_offset = 0 # will be added to the font size
|
||||
@ -189,6 +185,9 @@ ui.faces.debug = "(#__#)"
|
||||
ui.faces.upload = "(1__0)"
|
||||
ui.faces.upload1 = "(1__1)"
|
||||
ui.faces.upload2 = "(0__1)"
|
||||
ui.faces.png = false
|
||||
ui.faces.position_x = 0
|
||||
ui.faces.position_y = 34
|
||||
|
||||
ui.web.enabled = true
|
||||
ui.web.address = "::" # listening on both ipv4 and ipv6 - switch to 0.0.0.0 to listen on just ipv4
|
||||
@ -202,11 +201,6 @@ ui.display.enabled = false
|
||||
ui.display.rotation = 180
|
||||
ui.display.type = "waveshare_4"
|
||||
|
||||
bettercap.scheme = "http"
|
||||
bettercap.hostname = "localhost"
|
||||
bettercap.port = 8081
|
||||
bettercap.username = "pwnagotchi"
|
||||
bettercap.password = "pwnagotchi"
|
||||
bettercap.handshakes = "/root/handshakes"
|
||||
bettercap.silence = [
|
||||
"ble.device.new",
|
||||
@ -225,7 +219,7 @@ bettercap.silence = [
|
||||
|
||||
fs.memory.enabled = true
|
||||
fs.memory.mounts.log.enabled = true
|
||||
fs.memory.mounts.log.mount = "/home/pi/logs"
|
||||
fs.memory.mounts.log.mount = "/etc/pwnagotchi/log/"
|
||||
fs.memory.mounts.log.size = "50M"
|
||||
fs.memory.mounts.log.sync = 60
|
||||
fs.memory.mounts.log.zram = true
|
||||
|
@ -89,7 +89,7 @@ def update_data(last_session):
|
||||
'uname': subprocess.getoutput("uname -a"),
|
||||
'brain': brain,
|
||||
'version': pwnagotchi.__version__,
|
||||
'build': "Pwnagotchi-Torch by Jayofelony",
|
||||
'build': "Pwnagotchi by Jayofelony",
|
||||
'plugins': enabled,
|
||||
'language': language,
|
||||
'bettercap': subprocess.getoutput("bettercap -version").split(".\n\n")[1],
|
||||
|
Binary file not shown.
@ -1,14 +1,13 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Pwnagotchi display English to Esperanto.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
# FIRST AUTHOR MADE THIS IN YEAR 2024.
|
||||
#
|
||||
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-11-16 21:10+0100\n"
|
||||
"POT-Creation-Date: 2024-01-25 23:40+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -18,218 +17,219 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
msgstr "ZzzzZZzzzzZzzz"
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr ""
|
||||
msgstr "Sal, mi estas Pwnagotchi! Komencante…"
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr ""
|
||||
msgstr "Nova tago, nova ĉaso, nova wifi!"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr ""
|
||||
msgstr "Eniru la elektronikon!"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr ""
|
||||
msgstr "Mi pretas."
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr ""
|
||||
msgstr "La elektronika reto estas preta."
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr ""
|
||||
msgstr "Mi generas ŝlosilojn, ne malŝaltu min!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr ""
|
||||
msgstr "Hej, kanalo {channel} disponeblas! Via alirpunkto dankos vin"
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr ""
|
||||
msgstr "Legante protokolojn de antaŭa sesio…"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr ""
|
||||
msgstr "legi {lines_so_far} liniojn ĝis nun…"
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr ""
|
||||
msgstr "Mi enuas…"
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr ""
|
||||
msgstr "Ni iru promeni!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr ""
|
||||
msgstr "Plej bona tago de mia vivo!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr ""
|
||||
msgstr "Terura tago :/"
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr ""
|
||||
msgstr "mi estas tre enuigita…"
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr ""
|
||||
msgstr "Mi estas tre malĝoja…"
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr ""
|
||||
msgstr "Mi estas malfeliĉa"
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr ""
|
||||
msgstr "Lasu min sola…"
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr ""
|
||||
msgstr "Mi koleras kontraŭ vi!"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr ""
|
||||
msgstr "Mi ĝuas la vivon!"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr ""
|
||||
msgstr "Mi eniras tial mi estas."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr ""
|
||||
msgstr "Tiom da ludiloj!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr ""
|
||||
msgstr "Mi tre amuzas!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr ""
|
||||
msgstr "Scivolemo estas mia krimo…"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr ""
|
||||
msgstr "Sal {name}! Mi ĝojas renkonti vin."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr ""
|
||||
msgstr "Hej {name}! Sal?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr ""
|
||||
msgstr "Sal {name}! Kiel vi fartas?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr ""
|
||||
msgstr "Iu estas proksime! Ĝia nomo estas {name}."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr ""
|
||||
msgstr "Adiaŭ {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr ""
|
||||
msgstr "{name} malaperis…"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr ""
|
||||
msgstr "Hups… {name} malaperis…"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr ""
|
||||
msgstr "{name} mankis!"
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr ""
|
||||
msgstr "Maltrafis!"
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr ""
|
||||
msgstr "Bonaj amikoj estas beno!"
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr ""
|
||||
msgstr "Mi amas miajn amikojn!"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr ""
|
||||
msgstr "Neniu volas ludi kun mi..."
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr ""
|
||||
msgstr "Mi estas tiel sola..."
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr ""
|
||||
msgstr "KIE ĈIUJ ESTAS?!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr ""
|
||||
msgstr "Dormeto por {sec}j…"
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
msgstr "Zzzzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
msgstr "ZzzZzzz ({sec}j)"
|
||||
|
||||
msgid "Good night."
|
||||
msgstr ""
|
||||
msgstr "Bonan nokton"
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr ""
|
||||
msgstr "Zzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr ""
|
||||
msgstr "Atendas {sec}j…"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr ""
|
||||
msgstr "Ĉirkaŭrigardante ({sec}j)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr ""
|
||||
msgstr "Hej {what}, ni estu amikoj!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr ""
|
||||
msgstr "asociante al {what}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr ""
|
||||
msgstr "Hej {what}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr ""
|
||||
msgstr "Ĵus decidis, ke {mac} ne bezonas konekton!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr ""
|
||||
msgstr "Malaŭtentigi {mac}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr ""
|
||||
msgstr "Forigante {mac}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr ""
|
||||
msgstr "Mirinda, ni havas {num} novajn manpremojn!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "You have {count} new message{plural}!"
|
||||
msgstr ""
|
||||
msgstr "Vi nun havas {num} novajn mesaĝojn"
|
||||
|
||||
msgid "Oops, something went wrong ... Rebooting ..."
|
||||
msgstr ""
|
||||
msgstr "Lo okazis... Rekomencante…"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr ""
|
||||
msgstr "alŝuti datumojn al {to}…"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Downloading from {name} ..."
|
||||
msgstr ""
|
||||
msgstr "Elŝutu de {name}…"
|
||||
|
||||
#, python-brace-format
|
||||
#, fuzzy, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr ""
|
||||
msgstr "Forigita de {num} stacioj"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Made >999 new friends\n"
|
||||
msgstr ""
|
||||
msgstr "Faris pli ol 999 novajn amikojn!"
|
||||
|
||||
#, python-brace-format
|
||||
#, fuzzy, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr ""
|
||||
msgstr "faris (nombro) novajn amikojn"
|
||||
|
||||
#, python-brace-format
|
||||
#, fuzzy, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr ""
|
||||
msgstr "Ricevis {num} novajn manpremojn"
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr ""
|
||||
msgstr "Renkontita unu kolego"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr ""
|
||||
msgstr "renkontitajn {num} kolegojn"
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
@ -237,21 +237,24 @@ msgid ""
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
"Mi pwning dum {duration} kaj piedbatis {deauthed} klientojn!Mi ankaŭ "
|
||||
"renkontis {associated} novajn amikojn kaj manĝis {handshakes} manpremojn!"
|
||||
"#pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
|
||||
msgid "hours"
|
||||
msgstr ""
|
||||
msgstr "horror"
|
||||
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
msgstr "minutoj"
|
||||
|
||||
msgid "seconds"
|
||||
msgstr ""
|
||||
msgstr "sekundoj"
|
||||
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
msgstr "horo"
|
||||
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
msgstr "minuto"
|
||||
|
||||
msgid "second"
|
||||
msgstr ""
|
||||
msgstr "dua"
|
||||
|
Binary file not shown.
@ -36,18 +36,18 @@ msgid "The neural network is ready."
|
||||
msgstr "La red neuronal está lista."
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr ""
|
||||
msgstr "Generando llaves, no me apagues ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "¡Oye, el canal {channel} está libre! Tu AP lo agradecerá."
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr ""
|
||||
msgstr "Leyendo los logs de la última sesión ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr ""
|
||||
msgstr "Leyendo {lines_so_far} líneas de log hasta ahora ..."
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Estoy aburrido ..."
|
||||
@ -74,7 +74,7 @@ msgid "Leave me alone ..."
|
||||
msgstr "Me siento tan solo ..."
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr ""
|
||||
msgstr "Toy re enojado con vos!"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "¡Estoy viviendo la vida!"
|
||||
@ -97,11 +97,11 @@ msgstr "¡Hola {name}! Encantado de conocerte."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr ""
|
||||
msgstr "Que onda {name}!?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr ""
|
||||
msgstr "Eh!, ¿Que haces {name}?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
@ -127,10 +127,10 @@ msgid "Missed!"
|
||||
msgstr "¡Perdido!"
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr ""
|
||||
msgstr "Lxs buenxs amigxs son una masa!"
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr ""
|
||||
msgstr "¡Amo a mis amigxs!"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Nadie quiere jugar conmigo ..."
|
||||
@ -143,7 +143,7 @@ msgstr "¡¿Dónde está todo el mundo?!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Descansando durante {secs}s ..."
|
||||
msgstr "Descansando por {secs}s ..."
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr "Zzzzz"
|
||||
@ -203,7 +203,7 @@ msgstr "Oops, algo salió mal ... Reiniciando ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr ""
|
||||
msgstr "Subiendo data a {to} ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Downloading from {name} ..."
|
||||
|
Binary file not shown.
@ -16,7 +16,7 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
msgstr "ZzzzZZzzzzZzzz"
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "Ciao! Piacere Pwnagotchi! Caricamento ..."
|
||||
@ -25,7 +25,7 @@ msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Nuovo giorno...nuovi handshakes!!!"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr ""
|
||||
msgstr "Hack il Pianeta"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr "IA pronta."
|
||||
@ -34,18 +34,18 @@ msgid "The neural network is ready."
|
||||
msgstr "La rete neurale è pronta."
|
||||
|
||||
msgid "Generating keys, do not turn off ..."
|
||||
msgstr ""
|
||||
msgstr "Generazione di chiavi, non spegnere"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Hey, il canale {channel} è libero! Il tuo AP ringrazia."
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr ""
|
||||
msgstr "Lettura dei log dell'ultima sessione ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr ""
|
||||
msgstr "Leggi le righe di log {lines_so_far} finora ..."
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Che noia ..."
|
||||
@ -54,7 +54,7 @@ msgid "Let's go for a walk!"
|
||||
msgstr "Andiamo a fare una passeggiata!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Questo è il più bel giorno della mia vita!!!!"
|
||||
msgstr "Questo e il miglior giorno della mia vita!!!!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Giorno di merda :/"
|
||||
@ -72,22 +72,22 @@ msgid "Leave me alone ..."
|
||||
msgstr "Mi sento così solo..."
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr ""
|
||||
msgstr "sono arabiata con te"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Mi sento vivo!"
|
||||
msgstr "sono viva la vita!"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Pwn ergo sum."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Qui è pieno di reti!"
|
||||
msgstr "Qui pieno di reti!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Mi sto divertendo tantissimo!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr ""
|
||||
msgstr "Il mio crimine ? quello della curiosit?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
@ -95,15 +95,15 @@ msgstr "Ciao {name}! E' un piacere."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr ""
|
||||
msgstr "Yo {name} Come va"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr ""
|
||||
msgstr "Ehi {name} come stai?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr "L'Unità {name} è vicina!"
|
||||
msgstr "L'Unit {name} e vicina!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
@ -111,30 +111,30 @@ msgstr "Uhm ... addio {name}, mi mancherai..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name} se n'è andato ..."
|
||||
msgstr "{name} se andato ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Whoops ...{name} se n'è andato."
|
||||
msgstr "Whoops ...{name} se andato."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "{name} è scomparso..."
|
||||
msgstr "{name} scomparso..."
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr "Ehi! Dove sei andato!?"
|
||||
|
||||
msgid "Good friends are a blessing!"
|
||||
msgstr ""
|
||||
msgstr "Buoni amici sono una benedizione"
|
||||
|
||||
msgid "I love my friends!"
|
||||
msgstr ""
|
||||
msgstr "Amo i miei amici"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Nessuno vuole giocare con me..."
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Mi sento così solo..."
|
||||
msgstr "Mi sento cos solo..."
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Dove sono tutti?!"
|
||||
@ -144,17 +144,17 @@ msgid "Napping for {secs}s ..."
|
||||
msgstr "Schiaccio un pisolino per {secs}s ..."
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
msgstr "Zzzzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
msgstr "ZzzZzzz ({secs}s)"
|
||||
|
||||
msgid "Good night."
|
||||
msgstr ""
|
||||
msgstr "Buona notte"
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr ""
|
||||
msgstr "Zzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
@ -182,7 +182,7 @@ msgstr "Ho appena deciso che {mac} non necessita di WiFi!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr ""
|
||||
msgstr "Annullamento dell'autenticazione {mac}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
@ -201,11 +201,11 @@ msgstr "Ops, qualcosa è andato storto ... Riavvio ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uploading data to {to} ..."
|
||||
msgstr ""
|
||||
msgstr "Caricamento dei dati in {to}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Downloading from {name} ..."
|
||||
msgstr ""
|
||||
msgstr "Scaricamento da {name} ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
@ -255,4 +255,4 @@ msgid "minute"
|
||||
msgstr "minuto"
|
||||
|
||||
msgid "second"
|
||||
msgstr "secondo"
|
||||
msgstr "secondo"
|
Binary file not shown.
@ -14,7 +14,7 @@ msgstr ""
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: Portuguese (Brazil)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
|
@ -3,12 +3,12 @@
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-11-17 15:46+0100\n"
|
||||
"POT-Creation-Date: 2024-02-16 15:26-0300\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -199,6 +199,9 @@ def list_plugins(args, config, pattern='*'):
|
||||
available_not_installed = set(available.keys()) - set(installed.keys())
|
||||
|
||||
max_len_list = available_and_installed if args.installed else available_not_installed
|
||||
if not max_len_list:
|
||||
print('Maybe try: sudo pwnagotchi plugins update')
|
||||
return 1
|
||||
max_len = max(map(len, max_len_list))
|
||||
header = line.format(name='Plugin', width=max_len, version='Version', enabled='Active', status='Status')
|
||||
line_length = max(max_len, len('Plugin')) + len(header) - len('Plugin') - 12 # lol
|
||||
@ -239,7 +242,7 @@ def list_plugins(args, config, pattern='*'):
|
||||
print('-' * line_length)
|
||||
|
||||
if not found:
|
||||
logging.info('Maybe try: pwnagotchi plugins update')
|
||||
print('Maybe try: sudo pwnagotchi plugins update')
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
@ -28,7 +28,8 @@ def check(version, repo, native=True):
|
||||
resp = requests.get("https://api.github.com/repos/%s/releases/latest" % repo)
|
||||
latest = resp.json()
|
||||
info['available'] = latest_ver = latest['tag_name'].replace('v', '')
|
||||
is_arm64 = info['arch'].startswith('aarch')
|
||||
is_armhf = info['arch'].startswith('arm')
|
||||
is_aarch = info['arch'].startswith('aarch')
|
||||
|
||||
local = version_to_tuple(info['current'])
|
||||
remote = version_to_tuple(latest_ver)
|
||||
@ -36,12 +37,20 @@ def check(version, repo, native=True):
|
||||
if not native:
|
||||
info['url'] = "https://github.com/%s/archive/%s.zip" % (repo, latest['tag_name'])
|
||||
else:
|
||||
if is_arm64:
|
||||
# check if this release is compatible with aarch64
|
||||
if is_armhf:
|
||||
# check if this release is compatible with armhf
|
||||
for asset in latest['assets']:
|
||||
download_url = asset['browser_download_url']
|
||||
if (download_url.endswith('.zip') and
|
||||
(info['arch'] in download_url or (is_arm64 and 'aarch64' in download_url))):
|
||||
(info['arch'] in download_url or (is_armhf and 'armhf' in download_url))):
|
||||
info['url'] = download_url
|
||||
break
|
||||
elif is_aarch:
|
||||
# check if this release is compatible with arm64/aarch64
|
||||
for asset in latest['assets']:
|
||||
download_url = asset['browser_download_url']
|
||||
if (download_url.endswith('.zip') and
|
||||
(info['arch'] in download_url or (is_aarch and 'aarch' in download_url))):
|
||||
info['url'] = download_url
|
||||
break
|
||||
|
||||
@ -130,7 +139,7 @@ def install(display, update):
|
||||
source_path = "%s-%s" % (source_path, update['available'])
|
||||
|
||||
# setup.py is going to install data files for us
|
||||
os.system("cd %s && pip3 install . --no-cache-dir --break-system-packages" % source_path)
|
||||
os.system("cd %s && pip3 install . --break-system-packages" % source_path)
|
||||
return True
|
||||
|
||||
|
||||
@ -189,7 +198,7 @@ class AutoUpdate(plugins.Plugin):
|
||||
to_check = [
|
||||
('jayofelony/bettercap', parse_version('bettercap -version'), True, 'bettercap'),
|
||||
('jayofelony/pwngrid', parse_version('pwngrid -version'), True, 'pwngrid-peer'),
|
||||
('jayofelony/pwnagotchi-bookworm', pwnagotchi.__version__, False, 'pwnagotchi')
|
||||
('jayofelony/pwnagotchi', pwnagotchi.__version__, False, 'pwnagotchi')
|
||||
]
|
||||
|
||||
for repo, local_version, is_native, svc_name in to_check:
|
||||
|
@ -11,6 +11,10 @@ from pwnagotchi import plugins
|
||||
import pwnagotchi.ui.faces as faces
|
||||
from pwnagotchi.bettercap import Client
|
||||
|
||||
from pwnagotchi.ui.components import Text
|
||||
from pwnagotchi.ui.view import BLACK
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
|
||||
|
||||
class FixServices(plugins.Plugin):
|
||||
__author__ = 'jayofelony'
|
||||
@ -21,9 +25,6 @@ class FixServices(plugins.Plugin):
|
||||
__help__ = """
|
||||
Reload brcmfmac module when blindbug is detected, instead of rebooting. Adapted from WATCHDOG.
|
||||
"""
|
||||
__defaults__ = {
|
||||
'enabled': True,
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.options = dict()
|
||||
@ -34,7 +35,6 @@ class FixServices(plugins.Plugin):
|
||||
self.isReloadingMon = False
|
||||
self.connection = None
|
||||
self.LASTTRY = 0
|
||||
self._count = 0
|
||||
|
||||
def on_loaded(self):
|
||||
"""
|
||||
@ -43,26 +43,27 @@ class FixServices(plugins.Plugin):
|
||||
logging.info("[Fix_Services] plugin loaded.")
|
||||
|
||||
def on_ready(self, agent):
|
||||
last_lines = self.get_last_lines('journalctl', ['-n10', '-k'], 10)
|
||||
last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10', '-k'],
|
||||
stdout=subprocess.PIPE).stdout))[-10:])
|
||||
try:
|
||||
cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True)
|
||||
logging.info("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output))
|
||||
logging.debug("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output))
|
||||
if ",UP," in str(cmd_output):
|
||||
logging.info("wlan0mon is up.")
|
||||
logging.debug("wlan0mon is up.")
|
||||
|
||||
if len(self.pattern.findall(last_lines)) >= 3:
|
||||
if hasattr(agent, 'view'):
|
||||
display = agent.view()
|
||||
display.set('status', 'Blind-Bug detected. Restarting.')
|
||||
display.update(force=True)
|
||||
logging.info('[Fix_Services] Blind-Bug detected. Restarting.')
|
||||
logging.debug('[Fix_Services] Blind-Bug detected. Restarting.')
|
||||
try:
|
||||
self._tryTurningItOffAndOnAgain(agent)
|
||||
except Exception as err:
|
||||
logging.warning("[Fix_Services turnOffAndOn] %s" % repr(err))
|
||||
|
||||
else:
|
||||
logging.info("[Fix_Services] Logs look good!")
|
||||
logging.debug("[Fix_Services] Logs look good!")
|
||||
|
||||
except Exception as err:
|
||||
logging.error("[Fix_Services ip link show wlan0mon]: %s" % repr(err))
|
||||
@ -76,12 +77,12 @@ class FixServices(plugins.Plugin):
|
||||
# apparently this only gets messages from bettercap going to syslog, not from syslog
|
||||
def on_bcap_sys_log(self, agent, event):
|
||||
if re.search('wifi error while hopping to channel', event['data']['Message']):
|
||||
logging.info("[Fix_Services]SYSLOG MATCH: %s" % event['data']['Message'])
|
||||
logging.info("[Fix_Services]**** restarting wifi.recon")
|
||||
logging.debug("[Fix_Services]SYSLOG MATCH: %s" % event['data']['Message'])
|
||||
logging.debug("[Fix_Services]**** restarting wifi.recon")
|
||||
try:
|
||||
result = agent.run("wifi.recon off; wifi.recon on")
|
||||
if result["success"]:
|
||||
logging.info("[Fix_Services] wifi.recon flip: success!")
|
||||
logging.debug("[Fix_Services] wifi.recon flip: success!")
|
||||
if hasattr(agent, 'view'):
|
||||
display = agent.view()
|
||||
if display:
|
||||
@ -95,23 +96,14 @@ class FixServices(plugins.Plugin):
|
||||
logging.error("[Fix_Services]SYSLOG wifi.recon flip fail: %s" % err)
|
||||
self._tryTurningItOffAndOnAgain(agent)
|
||||
|
||||
def get_last_lines(self, command, args, n):
|
||||
try:
|
||||
process = subprocess.Popen([command] + args, stdout=subprocess.PIPE)
|
||||
output = TextIOWrapper(process.stdout)
|
||||
lines = output.readlines()
|
||||
last_n_lines = ''.join(lines[-n:])
|
||||
return last_n_lines
|
||||
except Exception as e:
|
||||
print(f"Error occurred: {e}")
|
||||
return None
|
||||
|
||||
def on_epoch(self, agent, epoch, epoch_data):
|
||||
|
||||
last_lines = self.get_last_lines('journalctl', ['-n10', '-k'], 10)
|
||||
other_last_lines = self.get_last_lines('journalctl', ['-n10'], 10)
|
||||
other_other_last_lines = self.get_last_lines('tail', ['-n10', '/home/pi/logs/pwnagotchi.log'], 10)
|
||||
|
||||
last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10', '-k'],
|
||||
stdout=subprocess.PIPE).stdout))[-10:])
|
||||
other_last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10'],
|
||||
stdout=subprocess.PIPE).stdout))[-10:])
|
||||
other_other_last_lines = ''.join(
|
||||
list(TextIOWrapper(subprocess.Popen(['tail', '-n10', '/var/log/pwnagotchi.log'],
|
||||
stdout=subprocess.PIPE).stdout))[-10:])
|
||||
# don't check if we ran a reset recently
|
||||
logging.debug("[Fix_Services]**** epoch")
|
||||
if time.time() - self.LASTTRY > 180:
|
||||
@ -122,12 +114,12 @@ class FixServices(plugins.Plugin):
|
||||
|
||||
# Look for pattern 1
|
||||
if len(self.pattern.findall(last_lines)) >= 3:
|
||||
logging.info("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines)
|
||||
logging.debug("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines)
|
||||
if hasattr(agent, 'view'):
|
||||
display = agent.view()
|
||||
display.set('status', 'Blind-Bug detected. Restarting.')
|
||||
display.update(force=True)
|
||||
logging.info('[Fix_Services] Blind-Bug detected. Restarting.')
|
||||
logging.debug('[Fix_Services] Blind-Bug detected. Restarting.')
|
||||
try:
|
||||
self._tryTurningItOffAndOnAgain(agent)
|
||||
except Exception as err:
|
||||
@ -135,17 +127,17 @@ class FixServices(plugins.Plugin):
|
||||
|
||||
# Look for pattern 2
|
||||
elif len(self.pattern2.findall(other_last_lines)) >= 5:
|
||||
logging.info("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines)
|
||||
logging.debug("[Fix_Services]**** Should trigger a reload of the wlan0mon device:\n%s" % last_lines)
|
||||
if hasattr(agent, 'view'):
|
||||
display = agent.view()
|
||||
display.set('status', 'Wifi channel stuck. Restarting recon.')
|
||||
display.update(force=True)
|
||||
logging.info('[Fix_Services] Wifi channel stuck. Restarting recon.')
|
||||
logging.debug('[Fix_Services] Wifi channel stuck. Restarting recon.')
|
||||
|
||||
try:
|
||||
result = agent.run("wifi.recon off; wifi.recon on")
|
||||
if result["success"]:
|
||||
logging.info("[Fix_Services] wifi.recon flip: success!")
|
||||
logging.debug("[Fix_Services] wifi.recon flip: success!")
|
||||
if display:
|
||||
display.update(force=True, new_data={"status": "Wifi recon flipped!",
|
||||
"face": faces.COOL})
|
||||
@ -159,7 +151,7 @@ class FixServices(plugins.Plugin):
|
||||
|
||||
# Look for pattern 3
|
||||
elif len(self.pattern3.findall(other_last_lines)) >= 1:
|
||||
logging.info("[Fix_Services] Firmware has halted or crashed. Restarting wlan0mon.")
|
||||
logging.debug("[Fix_Services] Firmware has halted or crashed. Restarting wlan0mon.")
|
||||
if hasattr(agent, 'view'):
|
||||
display = agent.view()
|
||||
display.set('status', 'Firmware has halted or crashed. Restarting wlan0mon.')
|
||||
@ -167,13 +159,13 @@ class FixServices(plugins.Plugin):
|
||||
try:
|
||||
# Run the monstart command to restart wlan0mon
|
||||
cmd_output = subprocess.check_output("monstart", shell=True)
|
||||
logging.info("[Fix_Services monstart]: %s" % repr(cmd_output))
|
||||
logging.debug("[Fix_Services monstart]: %s" % repr(cmd_output))
|
||||
except Exception as err:
|
||||
logging.error("[Fix_Services monstart]: %s" % repr(err))
|
||||
|
||||
# Look for pattern 4
|
||||
elif len(self.pattern4.findall(other_other_last_lines)) >= 3:
|
||||
logging.info("[Fix_Services] wlan0 is down!")
|
||||
logging.debug("[Fix_Services] wlan0 is down!")
|
||||
if hasattr(agent, 'view'):
|
||||
display = agent.view()
|
||||
display.set('status', 'Restarting wlan0 now!')
|
||||
@ -181,7 +173,7 @@ class FixServices(plugins.Plugin):
|
||||
try:
|
||||
# Run the monstart command to restart wlan0mon
|
||||
cmd_output = subprocess.check_output("monstart", shell=True)
|
||||
logging.info("[Fix_Services monstart]: %s" % repr(cmd_output))
|
||||
logging.debug("[Fix_Services monstart]: %s" % repr(cmd_output))
|
||||
except Exception as err:
|
||||
logging.error("[Fix_Services monstart]: %s" % repr(err))
|
||||
|
||||
@ -197,7 +189,7 @@ class FixServices(plugins.Plugin):
|
||||
elif level == "debug":
|
||||
logging.debug(message)
|
||||
else:
|
||||
logging.info(message)
|
||||
logging.debug(message)
|
||||
|
||||
if ui:
|
||||
ui.update(force=force, new_data=displayData)
|
||||
@ -212,7 +204,7 @@ class FixServices(plugins.Plugin):
|
||||
# avoid overlapping restarts, but allow it if it's been a while
|
||||
# (in case the last attempt failed before resetting "isReloadingMon")
|
||||
if self.isReloadingMon and (time.time() - self.LASTTRY) < 180:
|
||||
logging.info("[Fix_Services] Duplicate attempt ignored")
|
||||
logging.debug("[Fix_Services] Duplicate attempt ignored")
|
||||
else:
|
||||
self.isReloadingMon = True
|
||||
self.LASTTRY = time.time()
|
||||
@ -237,9 +229,9 @@ class FixServices(plugins.Plugin):
|
||||
# is it up?
|
||||
try:
|
||||
cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True)
|
||||
logging.info("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output))
|
||||
logging.debug("[Fix_Services ip link show wlan0mon]: %s" % repr(cmd_output))
|
||||
if ",UP," in str(cmd_output):
|
||||
logging.info("wlan0mon is up. Skip reset?")
|
||||
logging.debug("wlan0mon is up. Skip reset?")
|
||||
# not reliable, so don't skip just yet
|
||||
# print("wlan0mon is up. Skipping reset.")
|
||||
# self.isReloadingMon = False
|
||||
@ -260,7 +252,7 @@ class FixServices(plugins.Plugin):
|
||||
except Exception as err:
|
||||
logging.error("[Fix_Services wifi.recon off] error %s" % (repr(err)))
|
||||
|
||||
logging.info("[Fix_Services] recon paused. Now trying wlan0mon reload")
|
||||
logging.debug("[Fix_Services] recon paused. Now trying wlan0mon reload")
|
||||
|
||||
try:
|
||||
cmd_output = subprocess.check_output("monstop", shell=True)
|
||||
@ -276,14 +268,13 @@ class FixServices(plugins.Plugin):
|
||||
#
|
||||
# Future: while "not fixed yet": blah blah blah. if "max_attemts", then reboot like the old days
|
||||
#
|
||||
tries = 0
|
||||
tries = 1
|
||||
while tries < 3:
|
||||
try:
|
||||
# unload the module
|
||||
cmd_output = subprocess.check_output("sudo modprobe -r brcmfmac", shell=True)
|
||||
self.logPrintView("info", "[Fix_Services] unloaded brcmfmac", display,
|
||||
{"status": "Turning it off #%s" % tries, "face": faces.SMART})
|
||||
time.sleep(1 + tries)
|
||||
|
||||
# reload the module
|
||||
try:
|
||||
@ -291,28 +282,24 @@ class FixServices(plugins.Plugin):
|
||||
cmd_output = subprocess.check_output("sudo modprobe brcmfmac", shell=True)
|
||||
|
||||
self.logPrintView("info", "[Fix_Services] reloaded brcmfmac")
|
||||
time.sleep(10 + 4 * tries) # give it some time for wlan device to stabilize, or whatever
|
||||
|
||||
# success! now make the mon0
|
||||
try:
|
||||
cmd_output = subprocess.check_output("monstart", shell=True)
|
||||
self.logPrintView("info", "[Fix_Services interface add wlan0mon] worked #%s: %s"
|
||||
self.logPrintView("info", "[Fix_Services interface add wlan0mon worked #%s: %s"
|
||||
% (tries, cmd_output))
|
||||
time.sleep(tries + 5)
|
||||
try:
|
||||
# try accessing mon0 in bettercap
|
||||
result = connection.run("set wifi.interface wlan0mon")
|
||||
if "success" in result:
|
||||
logging.info("[Fix_Services set wifi.interface wlan0mon] worked!")
|
||||
self._count = self._count + 1
|
||||
time.sleep(1)
|
||||
logging.debug("[Fix_Services set wifi.interface wlan0mon worked!")
|
||||
# stop looping and get back to recon
|
||||
break
|
||||
else:
|
||||
logging.info(
|
||||
logging.debug(
|
||||
"[Fix_Services set wifi.interfaceface wlan0mon] failed? %s" % repr(result))
|
||||
except Exception as err:
|
||||
logging.info(
|
||||
logging.debug(
|
||||
"[Fix_Services set wifi.interface wlan0mon] except: %s" % repr(err))
|
||||
except Exception as cerr: #
|
||||
if not display:
|
||||
@ -331,11 +318,11 @@ class FixServices(plugins.Plugin):
|
||||
|
||||
tries = tries + 1
|
||||
if tries < 3:
|
||||
logging.info("[Fix_Services] wlan0mon didn't make it. trying again")
|
||||
logging.debug("[Fix_Services] wlan0mon didn't make it. trying again")
|
||||
if not display:
|
||||
print(" wlan0mon didn't make it. trying again")
|
||||
else:
|
||||
logging.info("[Fix_Services] wlan0mon loading failed, no choice but to reboot ..")
|
||||
logging.debug("[Fix_Services] wlan0mon loading failed, no choice but to reboot ..")
|
||||
pwnagotchi.reboot()
|
||||
|
||||
# exited the loop, so hopefully it loaded
|
||||
@ -345,14 +332,14 @@ class FixServices(plugins.Plugin):
|
||||
"face": faces.INTENSE})
|
||||
else:
|
||||
print("And back on again...")
|
||||
logging.info("[Fix_Services] wlan0mon back up")
|
||||
logging.debug("[Fix_Services] wlan0mon back up")
|
||||
else:
|
||||
self.LASTTRY = time.time()
|
||||
|
||||
time.sleep(8 + tries * 2) # give it a bit before restarting recon in bettercap
|
||||
self.isReloadingMon = False
|
||||
|
||||
logging.info("[Fix_Services] re-enable recon")
|
||||
logging.debug("[Fix_Services] re-enable recon")
|
||||
try:
|
||||
result = connection.run("wifi.clear; wifi.recon on")
|
||||
|
||||
@ -362,7 +349,7 @@ class FixServices(plugins.Plugin):
|
||||
"face": faces.HAPPY})
|
||||
else:
|
||||
print("I can see again")
|
||||
logging.info("[Fix_Services] wifi.recon on")
|
||||
logging.debug("[Fix_Services] wifi.recon on")
|
||||
self.LASTTRY = time.time() + 120 # 2-minute pause until next time.
|
||||
else:
|
||||
logging.error("[Fix_Services] wifi.recon did not start up")
|
||||
@ -373,13 +360,24 @@ class FixServices(plugins.Plugin):
|
||||
logging.error("[Fix_Services wifi.recon on] %s" % repr(err))
|
||||
pwnagotchi.reboot()
|
||||
|
||||
def on_unload(self, ui):
|
||||
# called to setup the ui elements
|
||||
def on_ui_setup(self, ui):
|
||||
with ui._lock:
|
||||
try:
|
||||
logging.info("[Fix_Services] unloaded")
|
||||
except Exception as err:
|
||||
logging.info("[Fix_Services] unload err %s " % repr(err))
|
||||
pass
|
||||
# add custom UI elements
|
||||
if "position" in self.options:
|
||||
pos = self.options['position'].split(',')
|
||||
pos = [int(x.strip()) for x in pos]
|
||||
else:
|
||||
pos = (ui.width() / 2 + 35, ui.height() - 11)
|
||||
|
||||
logging.debug("Got here")
|
||||
|
||||
# called when the ui is updated
|
||||
def on_ui_update(self, ui):
|
||||
return
|
||||
|
||||
def on_unload(self, ui):
|
||||
return
|
||||
|
||||
|
||||
# run from command line to brute force a reload
|
||||
|
@ -20,6 +20,7 @@ class GPS(plugins.Plugin):
|
||||
def __init__(self):
|
||||
self.running = False
|
||||
self.coordinates = None
|
||||
self.options = dict()
|
||||
|
||||
def on_loaded(self):
|
||||
logging.info(f"gps plugin loaded for {self.options['device']}")
|
||||
|
@ -58,8 +58,9 @@ class Grid(plugins.Plugin):
|
||||
self.total_messages = 0
|
||||
self.lock = Lock()
|
||||
|
||||
def is_excluded(self, what):
|
||||
for skip in self.options['exclude']:
|
||||
def is_excluded(self, what, agent):
|
||||
config = agent.config()
|
||||
for skip in config['main']['whitelist']:
|
||||
skip = skip.lower()
|
||||
what = what.lower()
|
||||
if skip in what or skip.replace(':', '') in what:
|
||||
@ -87,6 +88,7 @@ class Grid(plugins.Plugin):
|
||||
|
||||
def check_handshakes(self, agent):
|
||||
logging.debug("checking pcaps")
|
||||
config = agent.config()
|
||||
|
||||
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
|
||||
num_networks = len(pcap_files)
|
||||
@ -98,19 +100,19 @@ class Grid(plugins.Plugin):
|
||||
if self.options['report']:
|
||||
logging.info("grid: %d new networks to report" % num_new)
|
||||
logging.debug("self.options: %s" % self.options)
|
||||
logging.debug(" exclude: %s" % self.options['exclude'])
|
||||
logging.debug(" exclude: %s" % config['main']['whitelist'])
|
||||
|
||||
for pcap_file in pcap_files:
|
||||
net_id = os.path.basename(pcap_file).replace('.pcap', '')
|
||||
if net_id not in reported:
|
||||
if self.is_excluded(net_id):
|
||||
if self.is_excluded(net_id, agent):
|
||||
logging.debug("skipping %s due to exclusion filter" % pcap_file)
|
||||
self.set_reported(reported, net_id)
|
||||
continue
|
||||
|
||||
essid, bssid = parse_pcap(pcap_file)
|
||||
if bssid:
|
||||
if self.is_excluded(essid) or self.is_excluded(bssid):
|
||||
if self.is_excluded(essid, agent) or self.is_excluded(bssid, agent):
|
||||
logging.debug("not reporting %s due to exclusion filter" % pcap_file)
|
||||
self.set_reported(reported, net_id)
|
||||
else:
|
||||
|
@ -144,6 +144,9 @@ class MemTemp(plugins.Plugin):
|
||||
elif ui.is_waveshare2in7():
|
||||
h_pos = (192, 138)
|
||||
v_pos = (211, 122)
|
||||
elif ui.is_waveshare1in54V2():
|
||||
h_pos = (53, 77)
|
||||
v_pos = (154, 65)
|
||||
else:
|
||||
h_pos = (155, 76)
|
||||
v_pos = (175, 61)
|
||||
|
@ -117,7 +117,6 @@ class NetPos(plugins.Plugin):
|
||||
except OSError as os_e:
|
||||
logging.error("NET-POS: %s", os_e)
|
||||
|
||||
|
||||
def _get_netpos(self, agent):
|
||||
aps = agent.get_access_points()
|
||||
netpos = dict()
|
||||
|
@ -25,6 +25,7 @@ class OnlineHashCrack(plugins.Plugin):
|
||||
self.report = StatusFile('/root/.ohc_uploads', data_format='json')
|
||||
self.skip = list()
|
||||
self.lock = Lock()
|
||||
self.options = dict()
|
||||
|
||||
def on_loaded(self):
|
||||
"""
|
||||
@ -34,13 +35,9 @@ class OnlineHashCrack(plugins.Plugin):
|
||||
logging.error("OHC: Email isn't set. Can't upload to onlinehashcrack.com")
|
||||
return
|
||||
|
||||
if 'whitelist' not in self.options:
|
||||
self.options['whitelist'] = list()
|
||||
|
||||
self.ready = True
|
||||
logging.info("OHC: OnlineHashCrack plugin loaded.")
|
||||
|
||||
|
||||
def _upload_to_ohc(self, path, timeout=30):
|
||||
"""
|
||||
Uploads the file to onlinehashcrack.com
|
||||
@ -78,7 +75,6 @@ class OnlineHashCrack(plugins.Plugin):
|
||||
except OSError as os_e:
|
||||
raise os_e
|
||||
|
||||
|
||||
def on_webhook(self, path, request):
|
||||
import requests
|
||||
from flask import redirect
|
||||
@ -87,7 +83,6 @@ class OnlineHashCrack(plugins.Plugin):
|
||||
r = s.post('https://www.onlinehashcrack.com/dashboard', data={'emailTasks': self.options['email'], 'submit': ''})
|
||||
return redirect(r.url, code=302)
|
||||
|
||||
|
||||
def on_internet_available(self, agent):
|
||||
"""
|
||||
Called in manual mode when there's internet connectivity
|
||||
@ -105,7 +100,7 @@ class OnlineHashCrack(plugins.Plugin):
|
||||
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if
|
||||
filename.endswith('.pcap')]
|
||||
# pull out whitelisted APs
|
||||
handshake_paths = remove_whitelisted(handshake_paths, self.options['whitelist'])
|
||||
handshake_paths = remove_whitelisted(handshake_paths, config['main']['whitelist'])
|
||||
handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
|
||||
if handshake_new:
|
||||
logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onlinehashcrack.com")
|
||||
|
@ -15,6 +15,9 @@ class PawGPS(plugins.Plugin):
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'Saves GPS coordinates whenever an handshake is captured. The GPS data is get from PAW on android.'
|
||||
|
||||
def __init__(self):
|
||||
self.options = dict()
|
||||
|
||||
def on_loaded(self):
|
||||
logging.info("[paw-gps] plugin loaded")
|
||||
if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None) or (len('ip' in self.options and self.options['ip']) is 0):
|
||||
|
@ -1,9 +1,10 @@
|
||||
import sys
|
||||
|
||||
import pwnagotchi.plugins as plugins
|
||||
import logging
|
||||
import os
|
||||
import json
|
||||
import re
|
||||
import datetime
|
||||
from flask import Response
|
||||
from functools import lru_cache
|
||||
from dateutil.parser import parse
|
||||
@ -22,6 +23,7 @@ from dateutil.parser import parse
|
||||
|
||||
'''
|
||||
|
||||
|
||||
class Webgpsmap(plugins.Plugin):
|
||||
__author__ = 'https://github.com/xenDE and https://github.com/dadav'
|
||||
__version__ = '1.4.0'
|
||||
@ -103,7 +105,7 @@ class Webgpsmap(plugins.Plugin):
|
||||
response_status = 200
|
||||
response_mimetype = "application/xhtml+xml"
|
||||
response_header_contenttype = 'text/html'
|
||||
response_header_contentdisposition = 'attachment; filename=webgpsmap.html';
|
||||
response_header_contentdisposition = 'attachment; filename=webgpsmap.html'
|
||||
except Exception as error:
|
||||
logging.error(f"[webgpsmap] on_webhook offlinemap: error: {error}")
|
||||
return
|
||||
@ -149,7 +151,6 @@ class Webgpsmap(plugins.Plugin):
|
||||
def _get_pos_from_file(self, path):
|
||||
return PositionFile(path)
|
||||
|
||||
|
||||
def load_gps_from_dir(self, gpsdir, newest_only=False):
|
||||
"""
|
||||
Parses the gps-data from disk
|
||||
@ -160,13 +161,9 @@ class Webgpsmap(plugins.Plugin):
|
||||
|
||||
logging.info(f"[webgpsmap] scanning {handshake_dir}")
|
||||
|
||||
|
||||
all_files = os.listdir(handshake_dir)
|
||||
#print(all_files)
|
||||
all_pcap_files = [os.path.join(handshake_dir, filename)
|
||||
for filename in all_files
|
||||
if filename.endswith('.pcap')
|
||||
]
|
||||
# print(all_files)
|
||||
all_pcap_files = [os.path.join(handshake_dir, filename) for filename in all_files if filename.endswith('.pcap')]
|
||||
all_geo_or_gps_files = []
|
||||
for filename_pcap in all_pcap_files:
|
||||
filename_base = filename_pcap[:-5] # remove ".pcap"
|
||||
@ -300,7 +297,6 @@ class PositionFile:
|
||||
return parsed_ssid.groups()[0]
|
||||
return None
|
||||
|
||||
|
||||
def json(self):
|
||||
"""
|
||||
returns the parsed json
|
||||
|
@ -52,10 +52,10 @@ def _transform_wigle_entry(gps_data, pcap_data, plugin_version):
|
||||
"""
|
||||
dummy = StringIO()
|
||||
# write kismet header
|
||||
dummy.write(f"WigleWifi-1.4,appRelease={plugin_version},model=pwnagotchi,release={__pwnagotchi_version__},"
|
||||
f"device={pwnagotchi.name()},display=kismet,board=RaspberryPi,brand=pwnagotchi\n")
|
||||
dummy.write("MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,"
|
||||
"CurrentLongitude,AltitudeMeters,AccuracyMeters,Type\n")
|
||||
dummy.write(f"WigleWifi-1.6,appRelease={plugin_version},model=pwnagotchi,release={__pwnagotchi_version__},"
|
||||
f"device={pwnagotchi.name()},display=kismet,board=RaspberryPi,brand=pwnagotchi,star=Sol,body=3,subBody=0\n")
|
||||
dummy.write(
|
||||
"MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type\n")
|
||||
|
||||
writer = csv.writer(dummy, delimiter=",", quoting=csv.QUOTE_NONE, escapechar="\\")
|
||||
writer.writerow([
|
||||
@ -89,7 +89,7 @@ def _send_to_wigle(lines, api_key, donate=True, timeout=30):
|
||||
headers = {'Authorization': f"Basic {api_key}",
|
||||
'Accept': 'application/json'}
|
||||
data = {'donate': 'on' if donate else 'false'}
|
||||
payload = {'file': (pwnagotchi.name()+".csv", dummy), 'type': 'multipart/form-data'}
|
||||
payload = {'file': (pwnagotchi.name() + ".csv", dummy, 'multipart/form-data', {'Expires': '0'})}
|
||||
try:
|
||||
res = requests.post('https://api.wigle.net/api/v2/file/upload',
|
||||
data=data,
|
||||
@ -104,10 +104,10 @@ def _send_to_wigle(lines, api_key, donate=True, timeout=30):
|
||||
|
||||
|
||||
class Wigle(plugins.Plugin):
|
||||
__author__ = 'Dadav and fixed by Jayofelony'
|
||||
__version__ = '3.0.0'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'This plugin automatically uploads collected wifis to wigle.net'
|
||||
__author__ = "Dadav and updated by Jayofelony"
|
||||
__version__ = "3.0.1"
|
||||
__license__ = "GPL3"
|
||||
__description__ = "This plugin automatically uploads collected WiFi to wigle.net"
|
||||
|
||||
def __init__(self):
|
||||
self.ready = False
|
||||
@ -121,18 +121,15 @@ class Wigle(plugins.Plugin):
|
||||
logging.debug("WIGLE: api_key isn't set. Can't upload to wigle.net")
|
||||
return
|
||||
|
||||
if not 'whitelist' in self.options:
|
||||
self.options['whitelist'] = list()
|
||||
|
||||
if not 'donate' in self.options:
|
||||
self.options['donate'] = True
|
||||
if 'donate' not in self.options:
|
||||
self.options['donate'] = False
|
||||
|
||||
self.ready = True
|
||||
logging.info("WIGLE: ready")
|
||||
|
||||
def on_internet_available(self, agent):
|
||||
"""
|
||||
Called in manual mode when there's internet connectivity
|
||||
Called when there's internet connectivity
|
||||
"""
|
||||
if not self.ready or self.lock.locked():
|
||||
return
|
||||
@ -146,9 +143,9 @@ class Wigle(plugins.Plugin):
|
||||
all_files = os.listdir(handshake_dir)
|
||||
all_gps_files = [os.path.join(handshake_dir, filename)
|
||||
for filename in all_files
|
||||
if filename.endswith('.gps.json') or filename.endswith('.paw-gps.json') or filename.endswith('.geo.json')]
|
||||
if filename.endswith('.gps.json') or filename.endswith('.geo.json')]
|
||||
|
||||
all_gps_files = remove_whitelisted(all_gps_files, self.options['whitelist'])
|
||||
all_gps_files = remove_whitelisted(all_gps_files, config['main']['whitelist'])
|
||||
new_gps_files = set(all_gps_files) - set(reported) - set(self.skip)
|
||||
if new_gps_files:
|
||||
logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net")
|
||||
@ -157,8 +154,6 @@ class Wigle(plugins.Plugin):
|
||||
for gps_file in new_gps_files:
|
||||
if gps_file.endswith('.gps.json'):
|
||||
pcap_filename = gps_file.replace('.gps.json', '.pcap')
|
||||
if gps_file.endswith('.paw-gps.json'):
|
||||
pcap_filename = gps_file.replace('.paw-gps.json', '.pcap')
|
||||
if gps_file.endswith('.geo.json'):
|
||||
pcap_filename = gps_file.replace('.geo.json', '.pcap')
|
||||
if not os.path.exists(pcap_filename):
|
||||
|
@ -76,9 +76,6 @@ class WpaSec(plugins.Plugin):
|
||||
logging.error("WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured.")
|
||||
return
|
||||
|
||||
if 'whitelist' not in self.options:
|
||||
self.options['whitelist'] = list()
|
||||
|
||||
self.ready = True
|
||||
logging.info("WPA_SEC: plugin loaded")
|
||||
|
||||
@ -101,9 +98,8 @@ class WpaSec(plugins.Plugin):
|
||||
reported = self.report.data_field_or('reported', default=list())
|
||||
handshake_dir = config['bettercap']['handshakes']
|
||||
handshake_filenames = os.listdir(handshake_dir)
|
||||
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if
|
||||
filename.endswith('.pcap')]
|
||||
handshake_paths = remove_whitelisted(handshake_paths, self.options['whitelist'])
|
||||
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')]
|
||||
handshake_paths = remove_whitelisted(handshake_paths, config['main']['whitelist'])
|
||||
handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
|
||||
|
||||
if handshake_new:
|
||||
|
@ -1,4 +1,4 @@
|
||||
from PIL import Image
|
||||
from PIL import Image, ImageOps
|
||||
from textwrap import TextWrapper
|
||||
|
||||
|
||||
@ -40,21 +40,37 @@ class FilledRect(Widget):
|
||||
|
||||
|
||||
class Text(Widget):
|
||||
def __init__(self, value="", position=(0, 0), font=None, color=0, wrap=False, max_length=0):
|
||||
def __init__(self, value="", position=(0, 0), font=None, color=0, wrap=False, max_length=0, png=False):
|
||||
super().__init__(position, color)
|
||||
self.value = value
|
||||
self.font = font
|
||||
self.wrap = wrap
|
||||
self.max_length = max_length
|
||||
self.wrapper = TextWrapper(width=self.max_length, replace_whitespace=False) if wrap else None
|
||||
self.png = png
|
||||
|
||||
def draw(self, canvas, drawer):
|
||||
if self.value is not None:
|
||||
if self.wrap:
|
||||
text = '\n'.join(self.wrapper.wrap(self.value))
|
||||
if not self.png:
|
||||
if self.wrap:
|
||||
text = '\n'.join(self.wrapper.wrap(self.value))
|
||||
else:
|
||||
text = self.value
|
||||
drawer.text(self.xy, text, font=self.font, fill=self.color)
|
||||
else:
|
||||
text = self.value
|
||||
drawer.text(self.xy, text, font=self.font, fill=self.color)
|
||||
self.image = Image.open(self.value)
|
||||
self.image = self.image.convert('RGBA')
|
||||
self.pixels = self.image.load()
|
||||
for y in range(self.image.size[1]):
|
||||
for x in range(self.image.size[0]):
|
||||
if self.pixels[x,y][3] < 255: # check alpha
|
||||
self.pixels[x,y] = (255, 255, 255, 255)
|
||||
if self.color == 255:
|
||||
self._image = ImageOps.colorize(self.image.convert('L'), black = "white", white = "black")
|
||||
else:
|
||||
self._image = self.image
|
||||
self.image = self._image.convert('1')
|
||||
canvas.paste(self.image, self.xy)
|
||||
|
||||
|
||||
class LabeledValue(Widget):
|
||||
|
@ -23,7 +23,9 @@ DEBUG = '(#__#)'
|
||||
UPLOAD = '(1__0)'
|
||||
UPLOAD1 = '(1__1)'
|
||||
UPLOAD2 = '(0__1)'
|
||||
|
||||
PNG = False
|
||||
POSITION_X = 0
|
||||
POSITION_Y = 40
|
||||
|
||||
def load_from_config(config):
|
||||
for face_name, face_value in config.items():
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -12,6 +12,7 @@ from . import config
|
||||
from . import LCD_1in44
|
||||
from PIL import ImageOps
|
||||
|
||||
|
||||
class EPD(object):
|
||||
def __init__(self):
|
||||
self.width = 128
|
||||
@ -24,9 +25,8 @@ class EPD(object):
|
||||
pass
|
||||
|
||||
def clear(self):
|
||||
#self.LCD.LCD_Clear()
|
||||
pass
|
||||
self.LCD.LCD_Clear()
|
||||
|
||||
def display(self, image):
|
||||
rgb_im = ImageOps.colorize(image.convert("L"), black ="green", white ="black")
|
||||
rgb_im = ImageOps.colorize(image.convert("L"), black="green", white="black")
|
||||
self.LCD.LCD_ShowImage(rgb_im, 0, 0)
|
||||
|
@ -264,11 +264,11 @@ class EPD:
|
||||
def display(self, image):
|
||||
if (image == None):
|
||||
return
|
||||
# Width = (self.width % 8 == 0)? (self.width / 8 ): (self.width / 8 + 1)
|
||||
# Width = (self.width % 8 == 0)? (self.width // 8 ): (self.width // 8 + 1)
|
||||
if (self.width % 8 == 0):
|
||||
Width = self.width / 8
|
||||
Width = self.width // 8
|
||||
else:
|
||||
Width = self.width / 8 + 1
|
||||
Width = self.width // 8 + 1
|
||||
|
||||
self.send_command(0x10)
|
||||
for j in range(0, self.height):
|
||||
@ -282,11 +282,11 @@ class EPD:
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def Clear(self):
|
||||
# Width = (self.width % 8 == 0)? (self.width / 8 ): (self.width / 8 + 1)
|
||||
# Width = (self.width % 8 == 0)? (self.width // 8 ): (self.width // 8 + 1)
|
||||
if (self.width % 8 == 0):
|
||||
Width = self.width / 8
|
||||
Width = self.width // 8
|
||||
else:
|
||||
Width = self.width / 8 + 1
|
||||
Width = self.width // 8 + 1
|
||||
|
||||
Height = self.height
|
||||
|
||||
@ -313,11 +313,11 @@ class EPD:
|
||||
self.send_data(127) # y-end
|
||||
self.send_data(0x00)
|
||||
|
||||
# Width = (self.width % 8 == 0)? (self.width / 8 ): (self.width / 8 + 1)
|
||||
# Width = (self.width % 8 == 0)? (self.width // 8 ): (self.width // 8 + 1)
|
||||
if (self.width % 8 == 0):
|
||||
Width = self.width / 8
|
||||
Width = self.width // 8
|
||||
else:
|
||||
Width = self.width / 8 + 1
|
||||
Width = self.width // 8 + 1
|
||||
|
||||
Height = self.height
|
||||
# send data
|
||||
|
242
pwnagotchi/ui/hw/libs/waveshare/v2in23g/epd2in13g.py
Normal file
242
pwnagotchi/ui/hw/libs/waveshare/v2in23g/epd2in13g.py
Normal file
@ -0,0 +1,242 @@
|
||||
# *****************************************************************************
|
||||
# * | File : epd2in13g.py
|
||||
# * | Author : Waveshare team
|
||||
# * | Function : Electronic paper driver
|
||||
# * | Info :
|
||||
# *----------------
|
||||
# * | This version: V1.0
|
||||
# * | Date : 2023-05-29
|
||||
# # | Info : python demo
|
||||
# -----------------------------------------------------------------------------
|
||||
# ******************************************************************************/
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documnetation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
|
||||
import logging
|
||||
from .. import epdconfig
|
||||
|
||||
import PIL
|
||||
from PIL import Image
|
||||
import io
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 122
|
||||
EPD_HEIGHT = 250
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EPD:
|
||||
def __init__(self):
|
||||
self.reset_pin = epdconfig.RST_PIN
|
||||
self.dc_pin = epdconfig.DC_PIN
|
||||
self.busy_pin = epdconfig.BUSY_PIN
|
||||
self.cs_pin = epdconfig.CS_PIN
|
||||
self.width = EPD_WIDTH
|
||||
self.height = EPD_HEIGHT
|
||||
self.BLACK = 0x000000 # 00 BGR
|
||||
self.WHITE = 0xffffff # 01
|
||||
self.YELLOW = 0x00ffff # 10
|
||||
self.RED = 0x0000ff # 11
|
||||
self.Gate_BITS = EPD_HEIGHT
|
||||
if self.width < 128:
|
||||
self.Source_BITS = 128
|
||||
else:
|
||||
self.Source_BITS = self.width
|
||||
|
||||
# Hardware reset
|
||||
def reset(self):
|
||||
epdconfig.digital_write(self.reset_pin, 1)
|
||||
epdconfig.delay_ms(200)
|
||||
epdconfig.digital_write(self.reset_pin, 0) # module reset
|
||||
epdconfig.delay_ms(2)
|
||||
epdconfig.digital_write(self.reset_pin, 1)
|
||||
epdconfig.delay_ms(200)
|
||||
|
||||
def send_command(self, command):
|
||||
epdconfig.digital_write(self.dc_pin, 0)
|
||||
epdconfig.digital_write(self.cs_pin, 0)
|
||||
epdconfig.spi_writebyte([command])
|
||||
epdconfig.digital_write(self.cs_pin, 1)
|
||||
|
||||
def send_data(self, data):
|
||||
epdconfig.digital_write(self.dc_pin, 1)
|
||||
epdconfig.digital_write(self.cs_pin, 0)
|
||||
epdconfig.spi_writebyte([data])
|
||||
epdconfig.digital_write(self.cs_pin, 1)
|
||||
|
||||
def ReadBusy(self):
|
||||
logger.debug("e-Paper busy H")
|
||||
epdconfig.delay_ms(100)
|
||||
while (epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy
|
||||
epdconfig.delay_ms(5)
|
||||
logger.debug("e-Paper busy release")
|
||||
|
||||
def SetWindow(self):
|
||||
self.send_command(0x61) # SET_RAM_X_ADDRESS_START_END_POSITION
|
||||
# x point must be the multiple of 8 or the last 3 bits will be ignored
|
||||
self.send_data(self.Source_BITS / 256)
|
||||
self.send_data(self.Source_BITS % 256)
|
||||
self.send_data(self.Gate_BITS / 256)
|
||||
self.send_data(self.Gate_BITS % 256)
|
||||
|
||||
def TurnOnDisplay(self):
|
||||
self.send_command(0x12) # DISPLAY_REFRESH
|
||||
self.send_data(0X00)
|
||||
self.ReadBusy()
|
||||
|
||||
def init(self):
|
||||
if (epdconfig.module_init() != 0):
|
||||
return -1
|
||||
# EPD hardware init start
|
||||
|
||||
self.reset()
|
||||
|
||||
self.ReadBusy()
|
||||
self.send_command(0x4D)
|
||||
self.send_data(0x78)
|
||||
|
||||
self.send_command(0x00)
|
||||
self.send_data(0x0F)
|
||||
self.send_data(0x29)
|
||||
|
||||
self.send_command(0x01)
|
||||
self.send_data(0x07)
|
||||
self.send_data(0x00)
|
||||
|
||||
self.send_command(0x03)
|
||||
self.send_data(0x10)
|
||||
self.send_data(0x54)
|
||||
self.send_data(0x44)
|
||||
|
||||
self.send_command(0x06)
|
||||
self.send_data(0x05)
|
||||
self.send_data(0x00)
|
||||
self.send_data(0x3F)
|
||||
self.send_data(0x0A)
|
||||
self.send_data(0x25)
|
||||
self.send_data(0x12)
|
||||
self.send_data(0x1A)
|
||||
|
||||
self.send_command(0x50)
|
||||
self.send_data(0x37)
|
||||
|
||||
self.send_command(0x60)
|
||||
self.send_data(0x02)
|
||||
self.send_data(0x02)
|
||||
|
||||
self.SetWindow()
|
||||
|
||||
self.send_command(0xE7)
|
||||
self.send_data(0x1C)
|
||||
|
||||
self.send_command(0xE3)
|
||||
self.send_data(0x22)
|
||||
|
||||
self.send_command(0xB4)
|
||||
self.send_data(0xD0)
|
||||
self.send_command(0xB5)
|
||||
self.send_data(0x03)
|
||||
|
||||
self.send_command(0xE9)
|
||||
self.send_data(0x01)
|
||||
|
||||
self.send_command(0x30)
|
||||
self.send_data(0x08)
|
||||
|
||||
self.send_command(0x04)
|
||||
self.ReadBusy()
|
||||
return 0
|
||||
|
||||
def getbuffer(self, image):
|
||||
# Create a pallette with the 4 colors supported by the panel
|
||||
pal_image = Image.new("P", (1, 1))
|
||||
pal_image.putpalette((0, 0, 0, 255, 255, 255, 255, 255, 0, 255, 0, 0) + (0, 0, 0) * 252)
|
||||
|
||||
# Check if we need to rotate the image
|
||||
imwidth, imheight = image.size
|
||||
if (imwidth == self.width and imheight == self.height):
|
||||
image_temp = image
|
||||
elif (imwidth == self.height and imheight == self.width):
|
||||
image_temp = image.rotate(90, expand=True)
|
||||
else:
|
||||
logger.warning(
|
||||
"Invalid image dimensions: %d x %d, expected %d x %d" % (imwidth, imheight, self.width, self.height))
|
||||
|
||||
# Convert the soruce image to the 4 colors, dithering if needed
|
||||
image_4color = image_temp.convert("RGB").quantize(palette=pal_image)
|
||||
buf_4color = bytearray(image_4color.tobytes('raw'))
|
||||
|
||||
# into a single byte to transfer to the panel
|
||||
if self.width % 4 == 0:
|
||||
Width = self.width // 4
|
||||
else:
|
||||
Width = self.width // 4 + 1
|
||||
Height = self.height
|
||||
buf = [0x00] * int(Width * Height)
|
||||
idx = 0
|
||||
for j in range(0, Height):
|
||||
for i in range(0, Width):
|
||||
if i == Width - 1:
|
||||
buf[i + j * Width] = (buf_4color[idx] << 6) + (buf_4color[idx + 1] << 4)
|
||||
idx = idx + 2
|
||||
else:
|
||||
buf[i + j * Width] = (buf_4color[idx] << 6) + (buf_4color[idx + 1] << 4) + (
|
||||
buf_4color[idx + 2] << 2) + buf_4color[idx + 3]
|
||||
idx = idx + 4
|
||||
return buf
|
||||
|
||||
def display(self, image):
|
||||
if self.width % 4 == 0:
|
||||
Width = self.width // 4
|
||||
else:
|
||||
Width = self.width // 4 + 1
|
||||
Height = self.height
|
||||
|
||||
self.send_command(0x10)
|
||||
for j in range(0, Height):
|
||||
for i in range(0, self.Source_BITS // 4):
|
||||
if i < 31:
|
||||
self.send_data(image[i + j * Width])
|
||||
else:
|
||||
self.send_data(0x00)
|
||||
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def Clear(self, color=0x55):
|
||||
Width = self.Source_BITS // 4
|
||||
Height = self.height
|
||||
|
||||
self.send_command(0x10)
|
||||
for j in range(0, Height):
|
||||
for i in range(0, Width):
|
||||
self.send_data(color)
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def sleep(self):
|
||||
self.send_command(0x02) # POWER_OFF
|
||||
self.ReadBusy()
|
||||
epdconfig.delay_ms(100)
|
||||
|
||||
self.send_command(0x07) # DEEP_SLEEP
|
||||
self.send_data(0XA5)
|
||||
|
||||
epdconfig.delay_ms(2000)
|
||||
epdconfig.module_exit()
|
||||
### END OF FILE ###
|
@ -100,7 +100,7 @@ class EPD:
|
||||
|
||||
def ReadBusy(self):
|
||||
logger.debug("e-Paper busy")
|
||||
while epdconfig.digital_read(self.busy_pin) == 1: # 1: idle, 0: busy
|
||||
while (epdconfig.digital_read(self.busy_pin) == 1): # 1: idle, 0: busy
|
||||
epdconfig.delay_ms(20)
|
||||
logger.debug("e-Paper busy release")
|
||||
|
||||
@ -134,7 +134,7 @@ class EPD:
|
||||
self.send_data(self.LUT_DATA_4Gray[i])
|
||||
|
||||
def init(self):
|
||||
if epdconfig.module_init() != 0:
|
||||
if (epdconfig.module_init() != 0):
|
||||
return -1
|
||||
|
||||
# EPD hardware init start
|
||||
@ -159,7 +159,7 @@ class EPD:
|
||||
return 0
|
||||
|
||||
def init_Fast(self):
|
||||
if epdconfig.module_init() != 0:
|
||||
if (epdconfig.module_init() != 0):
|
||||
return -1
|
||||
|
||||
# EPD hardware init start
|
||||
@ -204,12 +204,12 @@ class EPD:
|
||||
return 0
|
||||
|
||||
def Init_4Gray(self):
|
||||
if epdconfig.module_init() != 0:
|
||||
if (epdconfig.module_init() != 0):
|
||||
return -1
|
||||
self.reset()
|
||||
|
||||
self.send_command(0x12) # soft reset
|
||||
self.ReadBusy()
|
||||
self.ReadBusy();
|
||||
|
||||
self.send_command(0x74) # set analog block control
|
||||
self.send_data(0x54)
|
||||
@ -268,14 +268,14 @@ class EPD:
|
||||
imwidth, imheight = image_monocolor.size
|
||||
pixels = image_monocolor.load()
|
||||
# logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
|
||||
if imwidth == self.width and imheight == self.height:
|
||||
if (imwidth == self.width and imheight == self.height):
|
||||
logger.debug("Vertical")
|
||||
for y in range(imheight):
|
||||
for x in range(imwidth):
|
||||
# Set the bits for the column of pixels at the current position.
|
||||
if pixels[x, y] == 0:
|
||||
buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8))
|
||||
elif imwidth == self.height and imheight == self.width:
|
||||
elif (imwidth == self.height and imheight == self.width):
|
||||
logger.debug("Horizontal")
|
||||
for y in range(imheight):
|
||||
for x in range(imwidth):
|
||||
@ -293,40 +293,40 @@ class EPD:
|
||||
pixels = image_monocolor.load()
|
||||
i = 0
|
||||
# logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
|
||||
if imwidth == self.width and imheight == self.height:
|
||||
if (imwidth == self.width and imheight == self.height):
|
||||
logger.debug("Vertical")
|
||||
for y in range(imheight):
|
||||
for x in range(imwidth):
|
||||
# Set the bits for the column of pixels at the current position.
|
||||
if pixels[x, y] == 0xC0:
|
||||
if (pixels[x, y] == 0xC0):
|
||||
pixels[x, y] = 0x80
|
||||
elif pixels[x, y] == 0x80:
|
||||
elif (pixels[x, y] == 0x80):
|
||||
pixels[x, y] = 0x40
|
||||
i = i + 1
|
||||
if i % 4 == 0:
|
||||
if (i % 4 == 0):
|
||||
buf[int((x + (y * self.width)) / 4)] = (
|
||||
(pixels[x - 3, y] & 0xc0) | (pixels[x - 2, y] & 0xc0) >> 2 | (
|
||||
pixels[x - 1, y] & 0xc0) >> 4 | (pixels[x, y] & 0xc0) >> 6)
|
||||
|
||||
elif imwidth == self.height and imheight == self.width:
|
||||
elif (imwidth == self.height and imheight == self.width):
|
||||
logger.debug("Horizontal")
|
||||
for x in range(imwidth):
|
||||
for y in range(imheight):
|
||||
newx = y
|
||||
newy = self.height - x - 1
|
||||
if pixels[x, y] == 0xC0:
|
||||
if (pixels[x, y] == 0xC0):
|
||||
pixels[x, y] = 0x80
|
||||
elif pixels[x, y] == 0x80:
|
||||
elif (pixels[x, y] == 0x80):
|
||||
pixels[x, y] = 0x40
|
||||
i = i + 1
|
||||
if i % 4 == 0:
|
||||
if (i % 4 == 0):
|
||||
buf[int((newx + (newy * self.width)) / 4)] = (
|
||||
(pixels[x, y - 3] & 0xc0) | (pixels[x, y - 2] & 0xc0) >> 2 | (
|
||||
pixels[x, y - 1] & 0xc0) >> 4 | (pixels[x, y] & 0xc0) >> 6)
|
||||
return buf
|
||||
|
||||
def Clear(self):
|
||||
if self.width % 8 == 0:
|
||||
if (self.width % 8 == 0):
|
||||
Width = self.width // 8
|
||||
else:
|
||||
Width = self.width // 8 + 1
|
||||
@ -338,7 +338,7 @@ class EPD:
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def display(self, image):
|
||||
if self.width % 8 == 0:
|
||||
if (self.width % 8 == 0):
|
||||
Width = self.width // 8
|
||||
else:
|
||||
Width = self.width // 8 + 1
|
||||
@ -350,7 +350,7 @@ class EPD:
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def display_Fast(self, image):
|
||||
if self.width % 8 == 0:
|
||||
if (self.width % 8 == 0):
|
||||
Width = self.width // 8
|
||||
else:
|
||||
Width = self.width // 8 + 1
|
||||
@ -362,7 +362,7 @@ class EPD:
|
||||
self.TurnOnDisplay_Fast()
|
||||
|
||||
def display_Base(self, image):
|
||||
if self.width % 8 == 0:
|
||||
if (self.width % 8 == 0):
|
||||
Width = self.width // 8
|
||||
else:
|
||||
Width = self.width // 8 + 1
|
||||
@ -379,7 +379,7 @@ class EPD:
|
||||
self.TurnOnDisplay()
|
||||
|
||||
def display_Base_color(self, color):
|
||||
if self.width % 8 == 0:
|
||||
if (self.width % 8 == 0):
|
||||
Width = self.width // 8
|
||||
else:
|
||||
Width = self.width // 8 + 1
|
||||
@ -396,7 +396,8 @@ class EPD:
|
||||
# self.TurnOnDisplay()
|
||||
|
||||
def display_Partial(self, Image, Xstart, Ystart, Xend, Yend):
|
||||
if (Xstart % 8 + Xend % 8 == 8 & Xstart % 8 > Xend % 8) | Xstart % 8 + Xend % 8 == 0 | (Xend - Xstart) % 8 == 0:
|
||||
if ((Xstart % 8 + Xend % 8 == 8 & Xstart % 8 > Xend % 8) | Xstart % 8 + Xend % 8 == 0 | (
|
||||
Xend - Xstart) % 8 == 0):
|
||||
Xstart = Xstart // 8
|
||||
Xend = Xend // 8
|
||||
else:
|
||||
@ -439,23 +440,23 @@ class EPD:
|
||||
self.send_command(0x24) # Write Black and White image to RAM
|
||||
for j in range(Height):
|
||||
for i in range(Width):
|
||||
if (j > Ystart - 1) & (j < (Yend + 1)) & (i > Xstart - 1) & (i < (Xend + 1)):
|
||||
if ((j > Ystart - 1) & (j < (Yend + 1)) & (i > Xstart - 1) & (i < (Xend + 1))):
|
||||
self.send_data(Image[i + j * Width])
|
||||
self.TurnOnDisplay_Partial()
|
||||
|
||||
def display_4Gray(self, image):
|
||||
self.send_command(0x24)
|
||||
for i in range(0, 5808): # 5808*4 46464
|
||||
for i in range(0, 48000): # 5808*4 46464
|
||||
temp3 = 0
|
||||
for j in range(0, 2):
|
||||
temp1 = image[i * 2 + j]
|
||||
for k in range(0, 2):
|
||||
temp2 = temp1 & 0xC0
|
||||
if temp2 == 0xC0:
|
||||
if (temp2 == 0xC0):
|
||||
temp3 |= 0x00
|
||||
elif temp2 == 0x00:
|
||||
elif (temp2 == 0x00):
|
||||
temp3 |= 0x01
|
||||
elif temp2 == 0x80:
|
||||
elif (temp2 == 0x80):
|
||||
temp3 |= 0x01
|
||||
else: # 0x40
|
||||
temp3 |= 0x00
|
||||
@ -463,31 +464,31 @@ class EPD:
|
||||
|
||||
temp1 <<= 2
|
||||
temp2 = temp1 & 0xC0
|
||||
if temp2 == 0xC0:
|
||||
if (temp2 == 0xC0):
|
||||
temp3 |= 0x00
|
||||
elif temp2 == 0x00:
|
||||
elif (temp2 == 0x00):
|
||||
temp3 |= 0x01
|
||||
elif temp2 == 0x80:
|
||||
elif (temp2 == 0x80):
|
||||
temp3 |= 0x01
|
||||
else: # 0x40
|
||||
temp3 |= 0x00
|
||||
if j != 1 or k != 1:
|
||||
if (j != 1 or k != 1):
|
||||
temp3 <<= 1
|
||||
temp1 <<= 2
|
||||
self.send_data(temp3)
|
||||
|
||||
self.send_command(0x26)
|
||||
for i in range(0, 5808): # 5808*4 46464
|
||||
for i in range(0, 48000): # 5808*4 46464
|
||||
temp3 = 0
|
||||
for j in range(0, 2):
|
||||
temp1 = image[i * 2 + j]
|
||||
for k in range(0, 2):
|
||||
temp2 = temp1 & 0xC0
|
||||
if temp2 == 0xC0:
|
||||
if (temp2 == 0xC0):
|
||||
temp3 |= 0x00
|
||||
elif temp2 == 0x00:
|
||||
elif (temp2 == 0x00):
|
||||
temp3 |= 0x01
|
||||
elif temp2 == 0x80:
|
||||
elif (temp2 == 0x80):
|
||||
temp3 |= 0x00
|
||||
else: # 0x40
|
||||
temp3 |= 0x01
|
||||
@ -495,15 +496,15 @@ class EPD:
|
||||
|
||||
temp1 <<= 2
|
||||
temp2 = temp1 & 0xC0
|
||||
if temp2 == 0xC0:
|
||||
if (temp2 == 0xC0):
|
||||
temp3 |= 0x00
|
||||
elif temp2 == 0x00:
|
||||
elif (temp2 == 0x00):
|
||||
temp3 |= 0x01
|
||||
elif temp2 == 0x80:
|
||||
elif (temp2 == 0x80):
|
||||
temp3 |= 0x00
|
||||
else: # 0x40
|
||||
temp3 |= 0x01
|
||||
if j != 1 or k != 1:
|
||||
if (j != 1 or k != 1):
|
||||
temp3 <<= 1
|
||||
temp1 <<= 2
|
||||
self.send_data(temp3)
|
||||
@ -516,3 +517,5 @@ class EPD:
|
||||
|
||||
epdconfig.delay_ms(2000)
|
||||
epdconfig.module_exit()
|
||||
|
||||
### END OF FILE ###
|
@ -28,7 +28,7 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from .. import epdconfig
|
||||
from . import epdconfig
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 128
|
||||
@ -227,7 +227,7 @@ class EPD:
|
||||
self.send_data((y >> 8) & 0xFF)
|
||||
|
||||
def init(self):
|
||||
if epdconfig.module_init() != 0:
|
||||
if (epdconfig.module_init() != 0):
|
||||
return -1
|
||||
# EPD hardware init start
|
||||
self.reset()
|
||||
|
@ -184,7 +184,7 @@ class EPD:
|
||||
|
||||
self.send_command(0x01) # Driver output control
|
||||
self.send_data((self.height - 1) % 256)
|
||||
self.send_data((self.height - 1) / 256)
|
||||
self.send_data((self.height - 1) // 256)
|
||||
self.send_data(0x00)
|
||||
|
||||
self.send_command(0x11) # data entry mode
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user