Compare commits

...

128 Commits

Author SHA1 Message Date
111787544f Merge remote-tracking branch 'origin/master' 2024-03-05 16:31:06 +01:00
efa5d8b197 Update build
Signed-off-by: jayofelony <oudshoorn.jeroen@gmail.com>
2024-03-05 16:31:00 +01:00
9e5fb49d7c Merge pull request #78 from rai68/master
Update view.py - bug fix, my self left a broken debug line in
2024-03-05 06:14:15 +01:00
Rai
cbbd7d5a6d Merge branch 'jayofelony:master' into master 2024-03-05 14:36:29 +10:00
Rai
90c5818123 Update view.py
Signed-off-by: Rai <58925163+rai68@users.noreply.github.com>
2024-03-05 13:23:30 +10:00
eb76ef4c54 Update build
Signed-off-by: jayofelony <oudshoorn.jeroen@gmail.com>
2024-03-04 21:21:48 +01:00
59e42daeb5 Update bash
Signed-off-by: jayofelony <oudshoorn.jeroen@gmail.com>
2024-03-04 21:13:33 +01:00
5b7014c68e Change MOTD 2024-03-04 21:01:23 +01:00
42cc3136ee Update wizard script 2024-03-04 20:54:25 +01:00
2a243870f7 Update wizard script 2024-03-04 20:49:38 +01:00
8f043ff5ef Update wizard script 2024-03-04 20:47:52 +01:00
255bbdbc08 Update wizard script 2024-03-04 20:40:16 +01:00
6f57b66cf4 Add display invert to --install 2024-03-04 20:35:09 +01:00
a4c25e9996 Add display invert to --install 2024-03-04 20:28:31 +01:00
9495d55296 Add display invert to --install 2024-03-04 20:25:22 +01:00
69e7503d67 Add display invert to --install 2024-03-04 20:21:41 +01:00
25cb1f2175 Add display invert to --install 2024-03-04 20:11:20 +01:00
54b7ce5d0d Add display invert to --install 2024-03-04 20:08:45 +01:00
1f9afd541d Add display invert to --install 2024-03-04 19:59:48 +01:00
87eae76a58 Add display invert to --install 2024-03-04 16:07:17 +01:00
6691257036 change small font size 2024-03-04 16:00:31 +01:00
2149c5dbdf remove color = black 2024-03-04 15:55:50 +01:00
77af772b4b remove color = black 2024-03-04 15:53:18 +01:00
14e4fc6d47 Add a interactive configuration installer 2024-03-04 15:48:52 +01:00
5be8580a59 Add a interactive configuration installer 2024-03-04 15:46:23 +01:00
f7a599ab8f Add a interactive configuration installer 2024-03-04 15:42:36 +01:00
5f907b236a Add a interactive configuration installer 2024-03-04 15:39:24 +01:00
bc92613700 Add a interactive configuration installer 2024-03-04 15:36:17 +01:00
501ec9ca2b Add a interactive configuration installer 2024-03-04 15:34:39 +01:00
e5e0180f3c Add a interactive configuration installer 2024-03-04 15:32:08 +01:00
ea60808700 Add a interactive configuration installer 2024-03-04 15:26:48 +01:00
a34db250b5 Add a interactive configuration installer 2024-03-04 15:24:45 +01:00
d29aca15a9 Add ui.invert = false to defaults 2024-03-04 15:24:17 +01:00
8531b89771 Add ui.invert = false to defaults 2024-03-04 13:02:33 +01:00
59d510d0e1 Merge pull request #77
Adds invertable BLACK/WHITE to config
2024-03-04 12:56:52 +01:00
913b1a6e1d Update 2.7 inch display 2024-03-04 12:55:12 +01:00
Rai
de2cdaa3c9 Update view.py
Signed-off-by: Rai <58925163+rai68@users.noreply.github.com>
2024-03-04 20:41:19 +10:00
Rai
f2cf34a8b9 Update view.py
Signed-off-by: Rai <58925163+rai68@users.noreply.github.com>
2024-03-04 20:39:55 +10:00
Rai
bbb46128fe Update view.py
Signed-off-by: Rai <58925163+rai68@users.noreply.github.com>
2024-03-04 20:39:20 +10:00
46713b6e73 Update view.py 2024-03-04 19:59:34 +10:00
aa2b09fb21 Update build 2024-03-01 10:19:32 +01:00
9125e43b20 Add display type variants, no new displays 2024-03-01 10:08:03 +01:00
7e4d926b14 Fix setup.py 2024-03-01 10:07:33 +01:00
6417ef5a78 Upgrade pip, setuptools, wheel 2024-03-01 10:07:25 +01:00
46c03063fe Version 2.8.6 2024-02-29 23:05:30 +01:00
d5384d5a81 Moved all reset logging to debug, except for plugin loaded.
Removed the counter from display
2024-02-29 23:01:05 +01:00
e800c66e57 Let auto-update check for armhf/aarch64 (pwngrid/bettercap) 2024-02-29 22:49:21 +01:00
e3a404cb39 Fix waveshare1in54.py 2024-02-29 10:23:15 +01:00
6cb6aaeb81 Merge remote-tracking branch 'origin/master' 2024-02-29 10:22:52 +01:00
5761dac073 Fix pwnagotchi plugins list error if no update has been ran first 2024-02-29 10:22:41 +01:00
3ada0628e1 Merge pull request #74 from Sniffleupagus/patch-1
Automatically remove invalid ai parameters leftover from tensorflow
2024-02-28 18:25:47 +01:00
9fa772c36a Automatically remove invalid ai parameters leftover from tensorflow
Ease transition from old Pwnagotchi running tensor flow to new running torch, but automatically dumping the invalid parameters.

Signed-off-by: Sniffleupagus <129890632+Sniffleupagus@users.noreply.github.com>
2024-02-28 09:58:41 -05:00
a92e66137c Fix build 2024-02-27 11:59:22 +01:00
a7e98cf166 Fix build 2024-02-27 10:24:44 +01:00
9a1a264a9f Fix waveshare1in54.py 2024-02-27 10:21:29 +01:00
8a0d482fe0 Make pwnlog point to correct log location 2024-02-27 10:19:30 +01:00
0cc320d31f Fix waveshare1in54.py 2024-02-27 10:12:38 +01:00
d841d2b649 Update build 2024-02-27 10:11:00 +01:00
966126a986 Update build 2024-02-27 10:10:06 +01:00
d67935fa6a Update build 2024-02-26 21:27:10 +01:00
46d867ce7f Update build 2024-02-26 21:20:56 +01:00
2e16069850 Update workflow 2024-02-26 20:36:16 +01:00
6e3cbbdd39 Update Makefile 2024-02-26 20:15:55 +01:00
a19bd6a181 Update setup.py 2024-02-26 19:38:54 +01:00
6c03d95724 Update build 2024-02-26 19:34:14 +01:00
60a9da9acc Update workflow 2024-02-26 13:09:15 +01:00
25aba0d73c Update workflow 2024-02-26 13:04:00 +01:00
ecde496534 Update workflow 2024-02-26 11:49:39 +01:00
7ebd4dd7a0 Update workflow 2024-02-26 10:48:21 +01:00
35f1707436 Update workflow 2024-02-26 08:58:06 +01:00
60ced12ea5 Update workflow 2024-02-26 08:56:37 +01:00
6082a0c169 Update workflow 2024-02-26 08:55:05 +01:00
a72c1b1628 Update workflow 2024-02-26 08:54:05 +01:00
e37a0bdc62 Update workflow 2024-02-26 08:52:17 +01:00
262f8bf551 Update workflow 2024-02-26 08:48:30 +01:00
5311dd9ddc Update workflow 2024-02-25 21:54:39 +01:00
bd9c44b4a3 Update workflow 2024-02-25 20:50:08 +01:00
3b01aec872 Update workflow 2024-02-25 20:33:09 +01:00
3e571bff2d Update workflow 2024-02-25 14:29:23 +01:00
391941e64a Update workflow 2024-02-25 14:28:09 +01:00
3fff6182ed Update workflow 2024-02-25 14:23:16 +01:00
73e0a1fce7 Update auto-update.py 2024-02-25 13:42:12 +01:00
9ba23d77d1 Update Makefile
Remove bananagotchi files
2024-02-25 13:35:23 +01:00
8be75627e9 Update build 2024-02-25 12:58:40 +01:00
c2a36aa678 Update README.md 2024-02-25 12:29:11 +01:00
fdf0a087f6 Update workflow 2024-02-25 12:23:56 +01:00
c6efa5df08 Update workflow 2024-02-25 12:11:47 +01:00
62327a711e Update workflow 2024-02-25 12:11:20 +01:00
bb460a9cc6 Update workflow 2024-02-25 12:07:11 +01:00
93b2322ab5 Update workflow 2024-02-25 12:05:14 +01:00
53a8af4711 Update workflow 2024-02-25 12:04:16 +01:00
ed5decffa0 Version 2.8.5 2024-02-25 12:00:29 +01:00
147cbfaa07 Version 2.8.5 2024-02-25 11:56:44 +01:00
ced9b4d5ee Update requirements.txt 2024-02-25 11:55:46 +01:00
472c3165ed Update workflow publisher 2024-02-25 11:50:56 +01:00
1578d13d98 Fix builder 2024-02-25 11:46:30 +01:00
0965e7eb3e Update image filename
Add pishrink to Makefile
2024-02-25 11:25:22 +01:00
b789c14f14 Change image filename, 32bit/64bit
Signed-off-by: jayofelony <oudshoorn.jeroen@gmail.com>
2024-02-25 11:18:19 +01:00
0855b44d59 Update everyting!
Signed-off-by: jayofelony <oudshoorn.jeroen@gmail.com>
2024-02-25 11:17:05 +01:00
55c6007d32 Update everyting!
Signed-off-by: jayofelony <oudshoorn.jeroen@gmail.com>
2024-02-25 11:10:59 +01:00
a7cf8a3383 Delete pwnagotchi/ui/hw/libs directory
Signed-off-by: Jayofelony <oudshoorn.jeroen@gmail.com>
2024-02-25 10:45:28 +01:00
faa48b2752 Change repo's
Signed-off-by: jayofelony <oudshoorn.jeroen@gmail.com>
2024-02-25 10:42:47 +01:00
d20619340c Edit build
Signed-off-by: jayofelony <oudshoorn.jeroen@gmail.com>
2024-02-25 10:10:07 +01:00
43a07fe969 Edit Makefile
Signed-off-by: jayofelony <oudshoorn.jeroen@gmail.com>
2024-02-25 10:07:02 +01:00
bcce22c164 Edit Makefile
Signed-off-by: jayofelony <oudshoorn.jeroen@gmail.com>
2024-02-25 10:06:35 +01:00
0264b7a7db Merge remote-tracking branch 'origin/master' 2024-02-24 11:08:19 +01:00
fba5dd0341 Correct layout for waveshare3in52.py
Signed-off-by: jayofelony <oudshoorn.jeroen@gmail.com>
2024-02-24 11:08:00 +01:00
dd7760efdf Merge pull request #56 from Sniffleupagus/DummyDisplay
Added DummyDisplay for arbitrary screen size when headless
2024-02-20 20:49:04 +01:00
f7cd0fb1fc Initialize as "DummyDisplay", not "inky"
Signed-off-by: Sniffleupagus <129890632+Sniffleupagus@users.noreply.github.com>
2024-02-20 11:18:15 -05:00
611a3e7fb5 fix for waveshare3in7.py 2024-02-15 20:51:55 +01:00
599c6211e4 version 2.8.4 2024-02-15 20:48:27 +01:00
eb48d29851 fix on 38 displays 2024-02-15 20:46:48 +01:00
0999b95be0 update build 2024-02-15 09:55:01 +01:00
ae351e5e9c update waveshare3in0g.py 2024-02-15 09:54:49 +01:00
fa58136c0e update waveshare3in7.py 2024-02-15 09:34:45 +01:00
3d5185f2c1 update waveshare3in7.py 2024-02-15 09:30:03 +01:00
b6bb7b9080 update waveshare3in7.py 2024-02-15 09:26:33 +01:00
20715351af revert back to drschottky 2024-02-14 08:53:54 +01:00
1d3c781d12 version 2.8.3 2024-02-13 23:25:55 +01:00
bd51b4330f version 2.8.3 2024-02-13 23:15:05 +01:00
f216a21132 update 2024-02-13 23:13:49 +01:00
5ae357c3dc Merge remote-tracking branch 'origin/master' 2024-02-13 23:11:10 +01:00
ed9afe6856 Revert "Changed from dphys-swapfile to zram-tools"
This reverts commit 26913016b9.
2024-02-13 23:10:53 +01:00
25d932fa9b Revert "zramswap"
This reverts commit 0dbd611ed5.
2024-02-13 23:10:52 +01:00
ebd25d50b0 Update README.md 2024-02-09 23:13:26 +01:00
27e784a5d7 update 2024-02-09 13:12:40 +01:00
c02efb5224 update Makefile 2024-02-09 13:11:02 +01:00
418dbf21e3 Added DummyDisplay for arbitrary screen size when headless 2024-02-07 15:16:26 -08:00
127 changed files with 2656 additions and 1136 deletions

1
.github/FUNDING.yml vendored
View File

@ -1,4 +1,3 @@
# These are supported funding model platforms # These are supported funding model platforms
patreon: pwnagotchi_torch
github: jayofelony github: jayofelony

View File

@ -8,205 +8,65 @@ on:
required: true required: true
jobs: jobs:
build:
publish:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4
- name: Remove unnecessary directories - name: Extract version from file
run: | id: get_version
sudo rm -rf /usr/share/dotnet run: |
sudo rm -rf /opt/ghc VERSION=$(cut -d "'" -f2 < pwnagotchi/_version.py)
sudo rm -rf /usr/local/share/boost echo "VERSION=$VERSION" >> $GITHUB_ENV
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
- name: Check disk space - name: Get latest tag
run: df -BG uses: actions-ecosystem/action-get-latest-tag@v1
id: get-latest-tag
- name: Checkout code - name: Set LAST_VERSION as an environment variable
uses: actions/checkout@v4 run: echo "LAST_VERSION=${{ steps.get-latest-tag.outputs.tag }}" >> $GITHUB_ENV
- name: Validate tag - name: Generate release notes
id: tag-setter id: generate_release_notes
run: | run: |
TAG=${{ github.event.inputs.version }} COMMITS=$(git log --merges --pretty=format:"* %s" $LAST_VERSION--$VERSION | sed 's/$/\\n/g')
if [[ $TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then CONTRIBUTORS=$(git shortlog -sn $LAST_VERSION--$VERSION | awk '{print "* @" $2}' | sed 's/$/\\n/g')
echo "Tag $TAG is valid." RELEASE_BODY="**Full Changelog**: https://github.com/jayofelony/pwnagotchi/compare/$LAST_VERSION...$VERSION"
echo "TAG=$TAG" >> $GITHUB_OUTPUT echo "RELEASE_BODY=$RELEASE_BODY" >> $GITHUB_ENV
else
echo "Tag $TAG is not a valid semantic version. Aborting."
exit 1
fi
- name: Set up Python - name: Install qemu dependencies
uses: actions/setup-python@v5 run: sudo apt update && sudo apt install qemu-user-static qemu-utils xz-utils -y
with:
python-version: 3.9
- name: Install dependencies - name: Build img file
run: | run: ls -la .; pwd; make all
sudo apt-get update && sudo apt-get install -y libdbus-1-dev curl unzip gettext qemu-utils qemu qemu-user-static binfmt-support
pip install -r requirements.txt
- name: Update QEMU - name: Transfer 32bit.img to docker and give permissions
run: | run: sudo chown runner:docker "pwnagotchi-32bit.img"
sudo update-binfmts --enable qemu-aarch64
echo $(ls /usr/bin/qemu-aarch64-static)
- name: Restart binfmt-support - name: Transfer 64bit.img to docker and give permissions
run: sudo service binfmt-support restart run: sudo chown runner:docker "pwnagotchi-64bit.img"
- name: Mount binfmt_misc - name: PiShrink
run: | run: |
if ! grep -qs '/proc/sys/fs/binfmt_misc ' /proc/mounts; then wget https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh
echo "Mounting binfmt_misc" chmod +x pishrink.sh
sudo mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc sudo mv pishrink.sh /usr/local/bin
fi find /home/runner/work/ -type f -name "*.img" -exec sudo pishrink.sh -aZ {} \;
- name: Restart binfmt-support - name: Change name of 32.img.xz to add version
run: sudo service binfmt-support restart run: mv "pwnagotchi-32bit.img.xz" "pwnagotchi-$VERSION-32bit.img.xz"
- name: Update Languages - name: Change name of 64.img.xz to add version
run: make update_langs run: mv "pwnagotchi-64bit.img.xz" "pwnagotchi-$VERSION-64bit.img.xz"
- name: Compile Languages - name: Release
run: make compile_langs uses: softprops/action-gh-release@v1
with:
- name: Check disk space prerelease: true
run: df -BG tag_name: v${{ env.VERSION }}
name: Pwnagotchi v${{ env.VERSION }}
- name: Check qemu-user-static package files: |
run: | pwnagotchi-${{ env.VERSION }}-32bit.img.xz
echo "Checking qemu-user-static package..." pwnagotchi-${{ env.VERSION }}-64bit.img.xz
dpkg -s qemu-user-static && echo "qemu-user-static is installed." || echo "qemu-user-static is NOT installed." body: ${{ env.RELEASE_BODY }}
- name: Check binfmt-support service
run: |
echo "Checking binfmt-support service..."
service binfmt-support status && echo "binfmt-support service is running." || echo "binfmt-support service is NOT running."
- name: Check binfmt_misc filesystem
run: |
echo "Checking binfmt_misc filesystem..."
mount | grep binfmt_misc && echo "binfmt_misc is mounted." || echo "binfmt_misc is NOT mounted."
echo $(ls /proc/sys/fs/binfmt_misc | grep qemu-aarch64)
- name: Run Makefile
run: make
env:
PWN_VERSION: ${{ steps.tag-setter.outputs.TAG }}
- 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 {} \;
- name: Compress .img files
run: |
find /home/runner/work/ -type f -name "*.img" -exec xz --no-warn {} \;
- name: Create tag
uses: actions/github-script@v7
with:
script: |
const version = "${{ steps.tag-setter.outputs.TAG }}"
console.log(`Creating tag ${version}`)
await github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `refs/tags/${version}`,
sha: context.sha
})
- name: Create Release
id: create_release
uses: actions/github-script@v7
with:
script: |
const tag = "${{ steps.tag-setter.outputs.TAG }}"
console.log(`Creating release with tag: ${tag}`)
const release = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: tag,
name: tag,
draft: false,
prerelease: true,
generate_release_notes: true
})
console.log(`Created release with id: ${release.data.id}`)
return release.data.id
- name: Upload Release Asset
id: upload-release-asset
uses: actions/github-script@v7
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
script: |
const fs = require('fs');
const path = require('path');
const release_id = "${{ steps.create_release.outputs.result }}";
const asset_content_type = 'application/octet-stream';
const distDir = '/home/runner/work/';
const uploadFile = async (filePath) => {
if (fs.lstatSync(filePath).isDirectory()) {
const files = fs.readdirSync(filePath);
for (const file of files) {
await uploadFile(path.join(filePath, file));
}
} else {
// Check if the file has a .xz extension
if (path.extname(filePath) === '.xz') {
console.log(`Uploading ${filePath}...`);
const asset_name = path.basename(filePath);
const asset_size = fs.statSync(filePath).size;
const asset = fs.createReadStream(filePath);
const response = await github.rest.repos.uploadReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release_id,
name: asset_name,
data: asset,
headers: {
'content-type': asset_content_type,
'content-length': asset_size
}
});
console.log(`Uploaded ${filePath}: ${response.data.browser_download_url}`);
}
}
}
await uploadFile(distDir);
- name: Update Release
uses: actions/github-script@v7
with:
script: |
const release_id = "${{ steps.create_release.outputs.result }}"
console.log(`Updating release with id: ${release_id}`)
github.rest.repos.updateRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release_id,
tag_name: "${{ steps.tag-setter.outputs.TAG }}",
name: "${{ steps.tag-setter.outputs.TAG }}",
draft: false,
prerelease: false
})
- name: Save environment variable
run: echo "${{ steps.tag-setter.outputs.TAG }}" > env_var.txt
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: env-var
path: env_var.txt

View File

@ -1,4 +1,4 @@
PACKER_VERSION := 1.10.0 PACKER_VERSION := 1.10.1
PWN_HOSTNAME := pwnagotchi PWN_HOSTNAME := pwnagotchi
PWN_VERSION := $(shell cut -d"'" -f2 < pwnagotchi/_version.py) PWN_VERSION := $(shell cut -d"'" -f2 < pwnagotchi/_version.py)
@ -25,7 +25,8 @@ ifneq (,$(UNSHARE))
UNSHARE := $(UNSHARE) --uts UNSHARE := $(UNSHARE) --uts
endif endif
all: clean image clean # sudo apt-get install qemu-user-static qemu-utils
all: clean packer image
update_langs: update_langs:
@for lang in pwnagotchi/locale/*/; do\ @for lang in pwnagotchi/locale/*/; do\
@ -39,30 +40,21 @@ compile_langs:
./scripts/language.sh compile $$(basename $$lang); \ ./scripts/language.sh compile $$(basename $$lang); \
done done
PACKER := ~/pwnagotchi/packer packer: clean
PACKER_URL := https://releases.hashicorp.com/packer/$(PACKER_VERSION)/packer_$(PACKER_VERSION)_linux_$(GOARCH).zip curl https://releases.hashicorp.com/packer/$(PACKER_VERSION)/packer_$(PACKER_VERSION)_linux_amd64.zip -o /tmp/packer.zip
$(PACKER): unzip /tmp/packer.zip -d /tmp
mkdir -p $(@D) sudo mv /tmp/packer /usr/bin/packer
curl -L "$(PACKER_URL)" -o $(PACKER).zip
unzip $(PACKER).zip -d $(@D)
rm $(PACKER).zip
chmod +x $@
SDIST := dist/pwnagotchi-$(PWN_VERSION).tar.gz image: clean packer
$(SDIST): setup.py pwnagotchi 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
python3 setup.py sdist
# Building the image requires packer, but don't rebuild the image just because packer updated. bullseye: clean packer
pwnagotchi: | $(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. bookworm: clean packer
pwnagotchi: $(SDIST) builder/pwnagotchi.json.pkr.hcl builder/raspberrypi64.yml $(shell find builder/data -type f) 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
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
clean: clean:
- rm -rf dist pwnagotchi.egg-info - rm -rf /tmp/packer*
- rm -f $(PACKER)

View File

@ -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: 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. Enter an appropriate name.
@ -19,7 +19,7 @@ Input http://localhost/ for Authorized redirect URIs.
Select the correct oauth scope: Select the correct oauth scope:
- drive.file - drive
- drive.install - drive.install
Click Create. Click Create.

View File

@ -1,30 +1,7 @@
# Pwnagotchi-Torch # Pwnagotchi
<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 is the main source for all forks:
**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!!.** - RPiZeroW (32bit)
- RPiZero2W, RPi3, RPi4, RPi5 (64bit)
The RPi5 can only be used headless currently. (without display.)
If you are using an older 32-bit version Raspberry Pi, ZeroWH, use this [fork](https://github.com/jayofelony/pwnagotchi-torch/releases/tag/v2.6.4) and make sure you download the `armhf` version.
---
Download the latest image file [here](https://github.com/jayofelony/pwnagotchi-bookworm/releases/tag/v2.7.9), and let it auto-update from here on out.
**Use RPi imager to flash, please don't flash a new user as this will mess with logs created.**
- Select `Use Custom Image`
- Browse for the downloaded image file
- Select No under `Use OS Customization`
SSH credentials are `pi/raspberry`.
# Donations:
I would like to thank
- [findingmoist](https://github.com/findingmoist)
- [kr4k0n](https://github.com/kr4k0n)
for donating!
[Pwnagotchi-Torch](https://www.patreon.com/pwnagotchi_torch)
[GH Sponsor](https://github.com/sponsors/jayofelony) [GH Sponsor](https://github.com/sponsors/jayofelony)

View File

@ -7,6 +7,7 @@ import sys
import toml import toml
import requests import requests
import os import os
import re
import pwnagotchi import pwnagotchi
from pwnagotchi import utils from pwnagotchi import utils
@ -18,89 +19,85 @@ from pwnagotchi import fs
from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple from pwnagotchi.utils import DottedTomlEncoder, parse_version as version_to_tuple
def pwnagotchi_cli(): def do_clear(display):
def do_clear(display): logging.info("clearing the display ...")
logging.info("clearing the display ...") display.clear()
display.clear() sys.exit(0)
sys.exit(0)
def do_manual_mode(agent):
logging.info("entering manual mode ...")
agent.mode = 'manual' def do_manual_mode(agent):
agent.last_session.parse(agent.view(), args.skip_session) logging.info("entering manual mode ...")
if not args.skip_session:
logging.info( agent.mode = 'manual'
"the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % ( agent.last_session.parse(agent.view(), args.skip_session)
agent.last_session.duration_human, if not args.skip_session:
agent.last_session.epochs, logging.info(
agent.last_session.train_epochs, "the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % (
agent.last_session.avg_reward, agent.last_session.duration_human,
agent.last_session.min_reward, agent.last_session.epochs,
agent.last_session.max_reward)) 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(): if grid.is_connected():
plugins.on('internet_available', agent) plugins.on('internet_available', agent)
def do_auto_mode(agent): except Exception as e:
logging.info("entering auto mode ...") if str(e).find("wifi.interface not set") > 0:
logging.exception("main loop exception due to unavailable wifi device, likely programmatically disabled (%s)", e)
agent.mode = 'auto' logging.info("sleeping 60 seconds then advancing to next epoch to allow for cleanup code to trigger")
agent.start() time.sleep(60)
config = agent.config()
while True:
try:
# recon on all channels
agent.recon()
# get nearby access points grouped by channel
channels = agent.get_access_points_by_channel()
# for each channel
for ch, aps in channels:
time.sleep(0.2) # This is to make sure it doesn't error (https://github.com/seemoo-lab/nexmon/issues/596)
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:
if ap['mac'][:13].lower in config['main']['whitelist'] or ap['hostname'] in config['main']['whitelist']:
logging.info(f"Found your MAC address {ap['mac']} - {config['main']['whitelist']}")
continue
# 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() 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): def add_parsers(parser):
""" """
Adds the plugins and google subcommands Adds the plugins and google subcommands
@ -137,6 +134,8 @@ def pwnagotchi_cli():
help="Print the configuration.") help="Print the configuration.")
# Jayofelony added these # 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, parser.add_argument('--check-update', dest="check_update", action="store_true", default=False,
help="Check for updates on Pwnagotchi. And tells current version.") help="Check for updates on Pwnagotchi. And tells current version.")
parser.add_argument('--donate', dest="donate", action="store_true", default=False, parser.add_argument('--donate', dest="donate", action="store_true", default=False,
@ -161,30 +160,128 @@ def pwnagotchi_cli():
print(pwnagotchi.__version__) print(pwnagotchi.__version__)
sys.exit(0) 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: 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) sys.exit(0)
if args.check_update: 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 = resp.json()
latest_ver = latest['tag_name'].replace('v', '') latest_ver = latest['tag_name'].replace('v', '')
local = version_to_tuple(pwnagotchi.__version__) local = version_to_tuple(pwnagotchi.__version__)
remote = version_to_tuple(latest_ver) remote = version_to_tuple(latest_ver)
if remote > local: 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)) % (pwnagotchi.__version__, latest_ver))
# input validation # input validation
if user_input.lower() in ('y', 'yes'): if user_input.lower() in ('y', 'yes'):
if os.path.exists('/root/.auto-update'): if os.path.exists('/root/.auto-update'):
os.system("rm /root/.auto-update && systemctl restart pwnagotchi") 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.") 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!") print("Okay, guess not!")
else:
print("Invalid input.")
else: else:
print("You are currently on the latest release, v%s." % pwnagotchi.__version__) print("You are currently on the latest release, v%s." % pwnagotchi.__version__)
sys.exit(0) sys.exit(0)
@ -229,7 +326,3 @@ def pwnagotchi_cli():
do_manual_mode(agent) do_manual_mode(agent)
else: else:
do_auto_mode(agent) do_auto_mode(agent)
if __name__ == '__main__':
pwnagotchi_cli()

View File

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

View File

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

View File

@ -0,0 +1,26 @@
# /etc/dphys-swapfile - user settings for dphys-swapfile package
# author Neil Franklin, last modification 2010.05.05
# copyright ETH Zuerich Physics Departement
# use under either modified/non-advertising BSD or GPL license
# this file is sourced with . so full normal sh syntax applies
# the default settings are added as commented out CONF_*=* lines
# where we want the swapfile to be, this is the default
#CONF_SWAPFILE=/var/swap
# set size to absolute value, leaving empty (default) then uses computed value
# you most likely don't want this, unless you have an special disk situation
CONF_SWAPSIZE=2048
# set size to computed value, this times RAM size, dynamically adapts,
# guarantees that there is enough swap without wasting disk space on excess
#CONF_SWAPFACTOR=2
# restrict size (computed and absolute!) to maximally this limit
# can be set to empty for no limit, but beware of filled partitions!
# this is/was a (outdated?) 32bit kernel limit (in MBytes), do not overrun it
# but is also sensible on 64bit to prevent filling /var or even / partition
#CONF_MAXSWAP=2048

View File

@ -0,0 +1,20 @@
[Unit]
Description=Bluetooth service
Documentation=man:bluetoothd(8)
ConditionPathIsDirectory=/sys/class/bluetooth
[Service]
Type=dbus
BusName=org.bluez
ExecStart=/usr/libexec/bluetooth/bluetoothd --noplugin=sap,a2dp
NotifyAccess=main
#WatchdogSec=10
#Restart=on-failure
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
LimitNPROC=1
ProtectHome=true
ProtectSystem=full
[Install]
WantedBy=bluetooth.target
Alias=dbus-org.bluez.service

View File

@ -0,0 +1,6 @@
[Unit]
After=hciuart.service bluetooth.service
Before=
[Service]
ExecStartPre=/bin/sleep 5

View File

@ -8,7 +8,7 @@ After=bettercap.service
Environment=LD_PRELOAD=/usr/local/lib/libpcap.so.1 Environment=LD_PRELOAD=/usr/local/lib/libpcap.so.1
Environment=LD_LIBRARY_PATH=/usr/local/lib Environment=LD_LIBRARY_PATH=/usr/local/lib
Type=simple 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 Restart=always
RestartSec=30 RestartSec=30

View File

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

View File

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

View File

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

View File

@ -0,0 +1,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

View File

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

View File

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

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

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

View File

@ -0,0 +1,36 @@
_show_complete()
{
local cur opts node_names all_options opt_line
all_options="
pwnagotchi -h --help -C --config -U --user-config --manual --skip-session --clear --debug --version --print-config --wizard --check-update --donate {plugins,google}
pwnagotchi plugins -h --help {list,install,enable,disable,uninstall,update,upgrade}
pwnagotchi plugins list -i --installed -h --help
pwnagotchi plugins install -h --help
pwnagotchi plugins uninstall -h --help
pwnagotchi plugins enable -h --help
pwnagotchi plugins disable -h --help
pwnagotchi plugins update -h --help
pwnagotchi plugins upgrade -h --help
pwnagotchi google -h --help {login,refresh}
pwnagotchi google login -h --help
pwnagotchi google refresh -h --help
"
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
# shellcheck disable=SC2124
cmd="${COMP_WORDS[@]:0:${#COMP_WORDS[@]}-1}"
opt_line="$(grep -m1 "$cmd" <<<"$all_options")"
if [[ ${cur} == -* ]] ; then
opts="$(echo "$opt_line" | tr ' ' '\n' | awk '/^ *-/{gsub("[^a-zA-Z0-9-]","",$1);print $1}')"
# shellcheck disable=SC2207
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
# shellcheck disable=SC2086
opts="$(echo $opt_line | grep -Po '{\K[^}]+' | tr ',' '\n')"
# shellcheck disable=SC2207
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
}
complete -F _show_complete pwnagotchi

View File

@ -0,0 +1,26 @@
# /etc/dphys-swapfile - user settings for dphys-swapfile package
# author Neil Franklin, last modification 2010.05.05
# copyright ETH Zuerich Physics Departement
# use under either modified/non-advertising BSD or GPL license
# this file is sourced with . so full normal sh syntax applies
# the default settings are added as commented out CONF_*=* lines
# where we want the swapfile to be, this is the default
#CONF_SWAPFILE=/var/swap
# set size to absolute value, leaving empty (default) then uses computed value
# you most likely don't want this, unless you have an special disk situation
CONF_SWAPSIZE=2048
# set size to computed value, this times RAM size, dynamically adapts,
# guarantees that there is enough swap without wasting disk space on excess
#CONF_SWAPFACTOR=2
# restrict size (computed and absolute!) to maximally this limit
# can be set to empty for no limit, but beware of filled partitions!
# this is/was a (outdated?) 32bit kernel limit (in MBytes), do not overrun it
# but is also sensible on 64bit to prevent filling /var or even / partition
#CONF_MAXSWAP=2048

View File

@ -0,0 +1,13 @@
[Unit]
Description=bettercap api.rest service.
Documentation=https://bettercap.org
Wants=network.target
[Service]
Type=simple
ExecStart=/usr/bin/bettercap-launcher
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,20 @@
[Unit]
Description=Bluetooth service
Documentation=man:bluetoothd(8)
ConditionPathIsDirectory=/sys/class/bluetooth
[Service]
Type=dbus
BusName=org.bluez
ExecStart=/usr/libexec/bluetooth/bluetoothd --noplugin=sap,a2dp
NotifyAccess=main
#WatchdogSec=10
#Restart=on-failure
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
LimitNPROC=1
ProtectHome=true
ProtectSystem=full
[Install]
WantedBy=bluetooth.target
Alias=dbus-org.bluez.service

View File

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

View File

@ -0,0 +1,16 @@
[Unit]
Description=pwngrid peer service.
Documentation=https://pwnagotchi.ai
Wants=network.target
After=bettercap.service
[Service]
Environment=LD_PRELOAD=/usr/local/lib/libpcap.so.1
Environment=LD_LIBRARY_PATH=/usr/local/lib
Type=simple
ExecStart=/usr/local/bin/pwngrid -keys /etc/pwnagotchi -peers /root/peers -address 127.0.0.1:8666 -client-token /root/.api-enrollment.json -wait -log /etc/pwnagotchi/log/pwngrid-peer.log -iface wlan0mon
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.target

View File

@ -20,6 +20,7 @@ echo " I'm managed by systemd. Here are some basic commands."
echo echo
echo " If you want to know what I'm doing, you can check my logs with the command" echo " If you want to know what I'm doing, you can check my logs with the command"
echo " - pwnlog" 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 --version, to check the current version"
echo " - sudo pwnagotchi --donate, to see how you can donate to this project" 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 " - sudo pwnagotchi --check-update, to see if there is a new version available"

View File

@ -6,7 +6,7 @@ packer {
} }
ansible = { ansible = {
source = "github.com/hashicorp/ansible" source = "github.com/hashicorp/ansible"
version = "~> 1" version = ">= 1.1.1"
} }
} }
} }
@ -25,7 +25,7 @@ source "arm" "rpi64-pwnagotchi" {
file_checksum_type = "sha256" file_checksum_type = "sha256"
file_target_extension = "xz" file_target_extension = "xz"
file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"] file_unarchive_cmd = ["unxz", "$ARCHIVE_PATH"]
image_path = "../../../pwnagotchi-rpi-bookworm-${var.pwn_version}-arm64.img" image_path = "../../../pwnagotchi-64bit.img"
qemu_binary_source_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P" qemu_binary_source_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P" qemu_binary_destination_path = "/usr/libexec/qemu-binfmt/aarch64-binfmt-P"
image_build_method = "resize" image_build_method = "resize"
@ -61,34 +61,30 @@ build {
provisioner "file" { provisioner "file" {
destination = "/usr/bin/" destination = "/usr/bin/"
sources = [ sources = [
"data/usr/bin/bettercap-launcher", "data/64bit/usr/bin/bettercap-launcher",
"data/usr/bin/hdmioff", "data/64bit/usr/bin/hdmioff",
"data/usr/bin/hdmion", "data/64bit/usr/bin/hdmion",
"data/usr/bin/monstart", "data/64bit/usr/bin/monstart",
"data/usr/bin/monstop", "data/64bit/usr/bin/monstop",
"data/usr/bin/pwnagotchi-launcher", "data/64bit/usr/bin/pwnagotchi-launcher",
"data/usr/bin/pwnlib", "data/64bit/usr/bin/pwnlib",
] ]
} }
provisioner "shell" { provisioner "shell" {
inline = ["chmod +x /usr/bin/*"] inline = ["chmod +x /usr/bin/*"]
} }
provisioner "shell" {
inline = ["dpkg --add-architecture armhf"]
}
provisioner "file" { provisioner "file" {
destination = "/etc/systemd/system/" destination = "/etc/systemd/system/"
sources = [ sources = [
"data/etc/systemd/system/bettercap.service", "data/64bit/etc/systemd/system/bettercap.service",
"data/etc/systemd/system/pwnagotchi.service", "data/64bit/etc/systemd/system/pwnagotchi.service",
"data/etc/systemd/system/pwngrid-peer.service", "data/64bit/etc/systemd/system/pwngrid-peer.service",
] ]
} }
provisioner "file" { provisioner "file" {
destination = "/etc/update-motd.d/01-motd" 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" { provisioner "shell" {
inline = ["chmod +x /etc/update-motd.d/*"] inline = ["chmod +x /etc/update-motd.d/*"]
@ -99,6 +95,6 @@ build {
provisioner "ansible-local" { provisioner "ansible-local" {
command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION=${var.pwn_version} PWN_HOSTNAME=${var.pwn_hostname} ansible-playbook" 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\""] extra_arguments = ["--extra-vars \"ansible_python_interpreter=/usr/bin/python3\""]
playbook_file = "raspberrypi64.yml" playbook_file = "data/64bit/raspberrypi64.yml"
} }
} }

View File

@ -10,7 +10,7 @@
full_pi5: "6.1.0-rpi8-rpi-2712" full_pi5: "6.1.0-rpi8-rpi-2712"
pwnagotchi: pwnagotchi:
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}" 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: system:
boot_options: boot_options:
- "dtoverlay=dwc2" - "dtoverlay=dwc2"
@ -27,7 +27,6 @@
- fstrim.timer - fstrim.timer
- pwnagotchi.service - pwnagotchi.service
- pwngrid-peer.service - pwngrid-peer.service
- zramswap.service
disable: disable:
- apt-daily-upgrade.service - apt-daily-upgrade.service
- apt-daily-upgrade.timer - apt-daily-upgrade.timer
@ -40,7 +39,7 @@
source: "https://github.com/jayofelony/caplets.git" source: "https://github.com/jayofelony/caplets.git"
bettercap: bettercap:
source: "https://github.com/jayofelony/bettercap.git" 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" ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
pwngrid: pwngrid:
source: "https://github.com/jayofelony/pwngrid.git" source: "https://github.com/jayofelony/pwngrid.git"
@ -78,6 +77,7 @@
- build-essential - build-essential
- curl - curl
- dkms - dkms
- dphys-swapfile
- fbi - fbi
- firmware-atheros - firmware-atheros
- firmware-brcm80211 - firmware-brcm80211
@ -102,7 +102,6 @@
- libbz2-dev - libbz2-dev
- libc-ares-dev - libc-ares-dev
- libc6-dev - libc6-dev
- libc6:armhf
- libcap-dev - libcap-dev
- libcurl-ocaml-dev - libcurl-ocaml-dev
- libdbus-1-dev - libdbus-1-dev
@ -117,10 +116,7 @@
- libgmp3-dev - libgmp3-dev
- libgstreamer1.0-0 - libgstreamer1.0-0
- libhdf5-dev - libhdf5-dev
- libisl23:armhf
- liblapack-dev - liblapack-dev
- libmpc3:armhf
- libmpfr6:armhf
- libncursesw5-dev - libncursesw5-dev
- libnetfilter-queue-dev - libnetfilter-queue-dev
- libopenblas-dev - libopenblas-dev
@ -135,7 +131,6 @@
- libsqlite3-dev - libsqlite3-dev
- libssl-dev - libssl-dev
- libssl-ocaml-dev - libssl-ocaml-dev
- libstdc++6:armhf
- libswscale5 - libswscale5
- libtiff6 - libtiff6
- libtool - libtool
@ -173,10 +168,8 @@
- wl - wl
- xxd - xxd
- zlib1g-dev - zlib1g-dev
- zram-tools
environment: environment:
ARCHFLAGS: "-arch aarch64" ARCHFLAGS: "-arch aarch64"
QEMU_UNAME: "{{ kernel.full }}"
tasks: tasks:
# First we install packages # First we install packages
@ -187,6 +180,12 @@
update_cache: yes update_cache: yes
install_recommends: false install_recommends: false
- 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 # Now we set up /boot/firmware
- name: Create pi user - name: Create pi user
copy: copy:
@ -298,22 +297,9 @@
state: absent state: absent
path: /usr/local/src/hcxtools path: /usr/local/src/hcxtools
# Install nexmon to fix wireless scanning (takes 2.5G of space)
- name: symlink 1
file:
src: "/usr/lib/arm-linux-gnueabihf/libisl.so.23.2.0"
dest: "/usr/lib/arm-linux-gnueabihf/libisl.so.10"
state: link
- name: symlink 2
file:
src: "/usr/lib/arm-linux-gnueabihf/libmpfr.so.6.2.0"
dest: "/usr/lib/arm-linux-gnueabihf/libmpfr.so.4"
state: link
- name: clone nexmon repository - name: clone nexmon repository
git: git:
repo: https://github.com/seemoo-lab/nexmon.git repo: https://github.com/DrSchottky/nexmon.git
dest: /usr/local/src/nexmon dest: /usr/local/src/nexmon
# FIRST WE BUILD DRIVER FOR RPi5 # FIRST WE BUILD DRIVER FOR RPi5
@ -366,7 +352,7 @@
- name: clone nexmon repository - name: clone nexmon repository
git: git:
repo: https://github.com/seemoo-lab/nexmon.git repo: https://github.com/DrSchottky/nexmon.git
dest: /usr/local/src/nexmon dest: /usr/local/src/nexmon
- name: make firmware, RPi4 - name: make firmware, RPi4
@ -479,7 +465,7 @@
- name: clone pwnagotchi repository - name: clone pwnagotchi repository
git: git:
repo: https://github.com/jayofelony/pwnagotchi-bookworm.git repo: https://github.com/jayofelony/pwnagotchi.git
dest: /usr/local/src/pwnagotchi dest: /usr/local/src/pwnagotchi
- name: build pwnagotchi wheel - name: build pwnagotchi wheel
@ -511,6 +497,11 @@
block: | block: |
export GOPATH=$HOME/go export GOPATH=$HOME/go
export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin 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 when: golang.changed
- name: download pwngrid - name: download pwngrid
@ -534,7 +525,7 @@
repo: "{{ packages.bettercap.source }}" repo: "{{ packages.bettercap.source }}"
dest: /usr/local/src/bettercap 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" shell: "export GOPATH=$HOME/go && export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin && go mod tidy && make && make install"
args: args:
executable: /bin/bash executable: /bin/bash
@ -587,11 +578,6 @@
path: /etc/pwnagotchi path: /etc/pwnagotchi
state: directory state: directory
- name: create log folder
file:
path: /home/pi/logs
state: directory
- name: check if user configuration exists - name: check if user configuration exists
stat: stat:
path: /etc/pwnagotchi/config.toml path: /etc/pwnagotchi/config.toml
@ -604,7 +590,7 @@
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost! # Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
# Example: # Example:
# ui.display.enabled = true # ui.display.enabled = true
# ui.display.type = "waveshare_2" # ui.display.type = "waveshare_4"
when: not user_config.stat.exists when: not user_config.stat.exists
- name: Delete motd - name: Delete motd
@ -617,24 +603,6 @@
state: absent state: absent
path: /etc/update-motd.d/10-uname 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 - name: add firmware packages to hold
dpkg_selections: dpkg_selections:
name: "{{ item }}" name: "{{ item }}"

View File

@ -0,0 +1,15 @@
client_config_backend: file
client_config_file: /root/client_secrets.json
client_config:
client_id: <YOUR CLIENT ID>
client_secret: <YOUR CLIENT SECRET>
save_credentials: True
save_credentials_backend: file
save_credentials_file: /root/credentials.json
get_refresh_token: True
oauth_scope:
- https://www.googleapis.com/auth/drive
- https://www.googleapis.com/auth/drive.install

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1 +1 @@
__version__ = '2.8.2' __version__ = '2.8.6'

View File

@ -23,6 +23,12 @@ def load(config, agent, epoch, from_disk=True):
from stable_baselines3 import A2C from stable_baselines3 import A2C
logging.debug("[AI] A2C imported in %.2fs" % (time.time() - start)) 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() start = time.time()
from stable_baselines3.a2c import MlpPolicy from stable_baselines3.a2c import MlpPolicy
logging.debug("[AI] MlpPolicy imported in %.2fs" % (time.time() - start)) logging.debug("[AI] MlpPolicy imported in %.2fs" % (time.time() - start))

View File

@ -18,10 +18,6 @@ main.custom_plugin_repos = [
main.custom_plugins = "/usr/local/share/pwnagotchi/custom-plugins/" main.custom_plugins = "/usr/local/share/pwnagotchi/custom-plugins/"
main.plugins.aircrackonly.enabled = true
main.plugins.hashie.enabled = true
main.plugins.auto-update.enabled = true main.plugins.auto-update.enabled = true
main.plugins.auto-update.install = true main.plugins.auto-update.install = true
main.plugins.auto-update.interval = 1 main.plugins.auto-update.interval = 1
@ -36,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.interval = 1
main.plugins.bt-tether.devices.android-phone.scantime = 10 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.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.android-phone.priority = 1
main.plugins.bt-tether.devices.ios-phone.enabled = false main.plugins.bt-tether.devices.ios-phone.enabled = false
@ -47,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.interval = 5
main.plugins.bt-tether.devices.ios-phone.scantime = 20 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.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.bt-tether.devices.ios-phone.priority = 999
main.plugins.fix_services.enabled = true main.plugins.fix_services.enabled = true
@ -118,7 +114,7 @@ main.no_restart = false
main.filter = "" 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.enabled = true
main.log.rotation.size = "10M" main.log.rotation.size = "10M"
@ -156,6 +152,10 @@ personality.bond_encounters_factor = 20000
personality.throttle_a = 0.4 personality.throttle_a = 0.4
personality.throttle_d = 0.9 personality.throttle_d = 0.9
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.fps = 0.0
ui.font.name = "DejaVuSansMono" # for japanese: fonts-japanese-gothic ui.font.name = "DejaVuSansMono" # for japanese: fonts-japanese-gothic
ui.font.size_offset = 0 # will be added to the font size ui.font.size_offset = 0 # will be added to the font size
@ -185,6 +185,9 @@ ui.faces.debug = "(#__#)"
ui.faces.upload = "(1__0)" ui.faces.upload = "(1__0)"
ui.faces.upload1 = "(1__1)" ui.faces.upload1 = "(1__1)"
ui.faces.upload2 = "(0__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.enabled = true
ui.web.address = "::" # listening on both ipv4 and ipv6 - switch to 0.0.0.0 to listen on just ipv4 ui.web.address = "::" # listening on both ipv4 and ipv6 - switch to 0.0.0.0 to listen on just ipv4
@ -216,7 +219,7 @@ bettercap.silence = [
fs.memory.enabled = true fs.memory.enabled = true
fs.memory.mounts.log.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.size = "50M"
fs.memory.mounts.log.sync = 60 fs.memory.mounts.log.sync = 60
fs.memory.mounts.log.zram = true fs.memory.mounts.log.zram = true

View File

@ -89,7 +89,7 @@ def update_data(last_session):
'uname': subprocess.getoutput("uname -a"), 'uname': subprocess.getoutput("uname -a"),
'brain': brain, 'brain': brain,
'version': pwnagotchi.__version__, 'version': pwnagotchi.__version__,
'build': "Pwnagotchi-Torch by Jayofelony", 'build': "Pwnagotchi by Jayofelony",
'plugins': enabled, 'plugins': enabled,
'language': language, 'language': language,
'bettercap': subprocess.getoutput("bettercap -version").split(".\n\n")[1], 'bettercap': subprocess.getoutput("bettercap -version").split(".\n\n")[1],

View File

@ -36,18 +36,18 @@ msgid "The neural network is ready."
msgstr "La red neuronal está lista." msgstr "La red neuronal está lista."
msgid "Generating keys, do not turn off ..." msgid "Generating keys, do not turn off ..."
msgstr "" msgstr "Generando llaves, no me apagues ..."
#, python-brace-format #, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks." msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "¡Oye, el canal {channel} está libre! Tu AP lo agradecerá." msgstr "¡Oye, el canal {channel} está libre! Tu AP lo agradecerá."
msgid "Reading last session logs ..." msgid "Reading last session logs ..."
msgstr "" msgstr "Leyendo los logs de la última sesión ..."
#, python-brace-format #, python-brace-format
msgid "Read {lines_so_far} log lines so far ..." 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 ..." msgid "I'm bored ..."
msgstr "Estoy aburrido ..." msgstr "Estoy aburrido ..."
@ -74,7 +74,7 @@ msgid "Leave me alone ..."
msgstr "Me siento tan solo ..." msgstr "Me siento tan solo ..."
msgid "I'm mad at you!" msgid "I'm mad at you!"
msgstr "" msgstr "Toy re enojado con vos!"
msgid "I'm living the life!" msgid "I'm living the life!"
msgstr "¡Estoy viviendo la vida!" msgstr "¡Estoy viviendo la vida!"
@ -97,11 +97,11 @@ msgstr "¡Hola {name}! Encantado de conocerte."
#, python-brace-format #, python-brace-format
msgid "Yo {name}! Sup?" msgid "Yo {name}! Sup?"
msgstr "" msgstr "Que onda {name}!?"
#, python-brace-format #, python-brace-format
msgid "Hey {name} how are you doing?" msgid "Hey {name} how are you doing?"
msgstr "" msgstr "Eh!, ¿Que haces {name}?"
#, python-brace-format #, python-brace-format
msgid "Unit {name} is nearby!" msgid "Unit {name} is nearby!"
@ -127,10 +127,10 @@ msgid "Missed!"
msgstr "¡Perdido!" msgstr "¡Perdido!"
msgid "Good friends are a blessing!" msgid "Good friends are a blessing!"
msgstr "" msgstr "Lxs buenxs amigxs son una masa!"
msgid "I love my friends!" msgid "I love my friends!"
msgstr "" msgstr "¡Amo a mis amigxs!"
msgid "Nobody wants to play with me ..." msgid "Nobody wants to play with me ..."
msgstr "Nadie quiere jugar conmigo ..." msgstr "Nadie quiere jugar conmigo ..."
@ -143,7 +143,7 @@ msgstr "¡¿Dónde está todo el mundo?!"
#, python-brace-format #, python-brace-format
msgid "Napping for {secs}s ..." msgid "Napping for {secs}s ..."
msgstr "Descansando durante {secs}s ..." msgstr "Descansando por {secs}s ..."
msgid "Zzzzz" msgid "Zzzzz"
msgstr "Zzzzz" msgstr "Zzzzz"
@ -203,7 +203,7 @@ msgstr "Oops, algo salió mal ... Reiniciando ..."
#, python-brace-format #, python-brace-format
msgid "Uploading data to {to} ..." msgid "Uploading data to {to} ..."
msgstr "" msgstr "Subiendo data a {to} ..."
#, python-brace-format #, python-brace-format
msgid "Downloading from {name} ..." msgid "Downloading from {name} ..."

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-01-25 23:40+0100\n" "POT-Creation-Date: 2024-02-16 15:26-0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@ -199,6 +199,9 @@ def list_plugins(args, config, pattern='*'):
available_not_installed = set(available.keys()) - set(installed.keys()) available_not_installed = set(available.keys()) - set(installed.keys())
max_len_list = available_and_installed if args.installed else available_not_installed 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)) max_len = max(map(len, max_len_list))
header = line.format(name='Plugin', width=max_len, version='Version', enabled='Active', status='Status') 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 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) print('-' * line_length)
if not found: if not found:
logging.info('Maybe try: pwnagotchi plugins update') print('Maybe try: sudo pwnagotchi plugins update')
return 1 return 1
return 0 return 0

View File

@ -1,73 +0,0 @@
import pwnagotchi.plugins as plugins
import logging
import subprocess
import string
import os
'''
Aircrack-ng needed, to install:
> apt-get install aircrack-ng
'''
class AircrackOnly(plugins.Plugin):
__author__ = 'pwnagotchi [at] rossmarks [dot] uk'
__version__ = '1.0.1'
__license__ = 'GPL3'
__description__ = 'confirm pcap contains handshake/PMKID or delete it'
def __init__(self):
self.text_to_set = ""
self.options = dict()
def on_ready(self):
return
def on_loaded(self):
logging.info("aircrackonly plugin loaded")
if 'face' not in self.options:
self.options['face'] = '(>.<)'
check = subprocess.run(
'/usr/bin/dpkg -l aircrack-ng | grep aircrack-ng | awk \'{print $2, $3}\'', shell=True,
stdout=subprocess.PIPE)
check = check.stdout.decode('utf-8').strip()
if check != "aircrack-ng <none>":
logging.info("aircrackonly: Found " + check)
else:
logging.warning("aircrack-ng is not installed!")
def on_handshake(self, agent, filename, access_point, client_station):
display = agent.view()
to_delete = 0
handshake_found = 0
result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "1 handshake" | awk \'{print $2}\''),
shell=True, stdout=subprocess.PIPE)
result = result.stdout.decode('utf-8').translate({ord(c): None for c in string.whitespace})
if result:
handshake_found = 1
logging.info("[AircrackOnly] contains handshake")
if handshake_found == 0:
result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "PMKID" | awk \'{print $2}\''),
shell=True, stdout=subprocess.PIPE)
result = result.stdout.decode('utf-8').translate({ord(c): None for c in string.whitespace})
if result:
logging.info("[AircrackOnly] contains PMKID")
else:
to_delete = 1
if to_delete == 1:
os.remove(filename)
self.text_to_set = "Removed an uncrackable pcap"
logging.warning("Removed uncrackable pcap " + filename)
display.update(force=True)
def on_ui_update(self, ui):
if self.text_to_set:
ui.set('face', self.options['face'])
ui.set('status', self.text_to_set)
self.text_to_set = ""

View File

@ -28,7 +28,8 @@ def check(version, repo, native=True):
resp = requests.get("https://api.github.com/repos/%s/releases/latest" % repo) resp = requests.get("https://api.github.com/repos/%s/releases/latest" % repo)
latest = resp.json() latest = resp.json()
info['available'] = latest_ver = latest['tag_name'].replace('v', '') 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']) local = version_to_tuple(info['current'])
remote = version_to_tuple(latest_ver) remote = version_to_tuple(latest_ver)
@ -36,12 +37,20 @@ def check(version, repo, native=True):
if not native: if not native:
info['url'] = "https://github.com/%s/archive/%s.zip" % (repo, latest['tag_name']) info['url'] = "https://github.com/%s/archive/%s.zip" % (repo, latest['tag_name'])
else: else:
if is_arm64: if is_armhf:
# check if this release is compatible with aarch64 # check if this release is compatible with armhf
for asset in latest['assets']: for asset in latest['assets']:
download_url = asset['browser_download_url'] download_url = asset['browser_download_url']
if (download_url.endswith('.zip') and 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 info['url'] = download_url
break break
@ -189,7 +198,7 @@ class AutoUpdate(plugins.Plugin):
to_check = [ to_check = [
('jayofelony/bettercap', parse_version('bettercap -version'), True, 'bettercap'), ('jayofelony/bettercap', parse_version('bettercap -version'), True, 'bettercap'),
('jayofelony/pwngrid', parse_version('pwngrid -version'), True, 'pwngrid-peer'), ('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: for repo, local_version, is_native, svc_name in to_check:

View File

@ -11,6 +11,10 @@ from pwnagotchi import plugins
import pwnagotchi.ui.faces as faces import pwnagotchi.ui.faces as faces
from pwnagotchi.bettercap import Client 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): class FixServices(plugins.Plugin):
__author__ = 'jayofelony' __author__ = 'jayofelony'
@ -21,19 +25,16 @@ class FixServices(plugins.Plugin):
__help__ = """ __help__ = """
Reload brcmfmac module when blindbug is detected, instead of rebooting. Adapted from WATCHDOG. Reload brcmfmac module when blindbug is detected, instead of rebooting. Adapted from WATCHDOG.
""" """
__defaults__ = {
'enabled': True,
}
def __init__(self): def __init__(self):
self.options = dict() self.options = dict()
self.pattern1 = re.compile(r'wifi error while hopping to channel') self.pattern = re.compile(r'brcmf_cfg80211_nexmon_set_channel.*?Set Channel failed')
self.pattern2 = re.compile(r'Firmware has halted or crashed') self.pattern2 = re.compile(r'wifi error while hopping to channel')
self.pattern3 = re.compile(r'error 400: could not find interface wlan0mon') self.pattern3 = re.compile(r'Firmware has halted or crashed')
self.pattern4 = re.compile(r'error 400: could not find interface wlan0mon')
self.isReloadingMon = False self.isReloadingMon = False
self.connection = None self.connection = None
self.LASTTRY = 0 self.LASTTRY = 0
self._count = 0
def on_loaded(self): def on_loaded(self):
""" """
@ -42,14 +43,27 @@ class FixServices(plugins.Plugin):
logging.info("[Fix_Services] plugin loaded.") logging.info("[Fix_Services] plugin loaded.")
def on_ready(self, agent): 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: try:
cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True) cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True)
logging.debug("[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): if ",UP," in str(cmd_output):
logging.info("wlan0mon is up.") logging.debug("wlan0mon is up.")
logging.info("[Fix_Services] Logs look good!") 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.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.debug("[Fix_Services] Logs look good!")
except Exception as err: except Exception as err:
logging.error("[Fix_Services ip link show wlan0mon]: %s" % repr(err)) logging.error("[Fix_Services ip link show wlan0mon]: %s" % repr(err))
@ -63,12 +77,12 @@ class FixServices(plugins.Plugin):
# apparently this only gets messages from bettercap going to syslog, not from syslog # apparently this only gets messages from bettercap going to syslog, not from syslog
def on_bcap_sys_log(self, agent, event): def on_bcap_sys_log(self, agent, event):
if re.search('wifi error while hopping to channel', event['data']['Message']): if re.search('wifi error while hopping to channel', event['data']['Message']):
logging.info("[Fix_Services]SYSLOG MATCH: %s" % event['data']['Message']) logging.debug("[Fix_Services]SYSLOG MATCH: %s" % event['data']['Message'])
logging.info("[Fix_Services]**** restarting wifi.recon") logging.debug("[Fix_Services]**** restarting wifi.recon")
try: try:
result = agent.run("wifi.recon off; wifi.recon on") result = agent.run("wifi.recon off; wifi.recon on")
if result["success"]: if result["success"]:
logging.info("[Fix_Services] wifi.recon flip: success!") logging.debug("[Fix_Services] wifi.recon flip: success!")
if hasattr(agent, 'view'): if hasattr(agent, 'view'):
display = agent.view() display = agent.view()
if display: if display:
@ -82,23 +96,14 @@ class FixServices(plugins.Plugin):
logging.error("[Fix_Services]SYSLOG wifi.recon flip fail: %s" % err) logging.error("[Fix_Services]SYSLOG wifi.recon flip fail: %s" % err)
self._tryTurningItOffAndOnAgain(agent) 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): def on_epoch(self, agent, epoch, epoch_data):
last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10', '-k'],
last_lines = self.get_last_lines('journalctl', ['-n10', '-k'], 10) stdout=subprocess.PIPE).stdout))[-10:])
other_last_lines = self.get_last_lines('journalctl', ['-n10'], 10) other_last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl', '-n10'],
other_other_last_lines = self.get_last_lines('tail', ['-n10', '/home/pi/logs/pwnagotchi.log'], 10) 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 # don't check if we ran a reset recently
logging.debug("[Fix_Services]**** epoch") logging.debug("[Fix_Services]**** epoch")
if time.time() - self.LASTTRY > 180: if time.time() - self.LASTTRY > 180:
@ -108,18 +113,31 @@ class FixServices(plugins.Plugin):
logging.debug("[Fix_Services]**** checking") logging.debug("[Fix_Services]**** checking")
# Look for pattern 1 # Look for pattern 1
if len(self.pattern1.findall(other_last_lines)) >= 5: if len(self.pattern.findall(last_lines)) >= 3:
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.debug('[Fix_Services] Blind-Bug detected. Restarting.')
try:
self._tryTurningItOffAndOnAgain(agent)
except Exception as err:
logging.warning("[Fix_Services] TTOAOA: %s" % repr(err))
# Look for pattern 2
elif len(self.pattern2.findall(other_last_lines)) >= 5:
logging.debug("[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'): if hasattr(agent, 'view'):
display = agent.view() display = agent.view()
display.set('status', 'Wifi channel stuck. Restarting recon.') display.set('status', 'Wifi channel stuck. Restarting recon.')
display.update(force=True) display.update(force=True)
logging.info('[Fix_Services] Wifi channel stuck. Restarting recon.') logging.debug('[Fix_Services] Wifi channel stuck. Restarting recon.')
try: try:
result = agent.run("wifi.recon off; wifi.recon on") result = agent.run("wifi.recon off; wifi.recon on")
if result["success"]: if result["success"]:
logging.info("[Fix_Services] wifi.recon flip: success!") logging.debug("[Fix_Services] wifi.recon flip: success!")
if display: if display:
display.update(force=True, new_data={"status": "Wifi recon flipped!", display.update(force=True, new_data={"status": "Wifi recon flipped!",
"face": faces.COOL}) "face": faces.COOL})
@ -131,9 +149,9 @@ class FixServices(plugins.Plugin):
except Exception as err: except Exception as err:
logging.error("[Fix_Services wifi.recon flip] %s" % repr(err)) logging.error("[Fix_Services wifi.recon flip] %s" % repr(err))
# Look for pattern 2 # Look for pattern 3
elif len(self.pattern2.findall(other_last_lines)) >= 1: 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'): if hasattr(agent, 'view'):
display = agent.view() display = agent.view()
display.set('status', 'Firmware has halted or crashed. Restarting wlan0mon.') display.set('status', 'Firmware has halted or crashed. Restarting wlan0mon.')
@ -145,9 +163,9 @@ class FixServices(plugins.Plugin):
except Exception as err: except Exception as err:
logging.error("[Fix_Services monstart]: %s" % repr(err)) logging.error("[Fix_Services monstart]: %s" % repr(err))
# Look for pattern 3 # Look for pattern 4
elif len(self.pattern3.findall(other_other_last_lines)) >= 3: 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'): if hasattr(agent, 'view'):
display = agent.view() display = agent.view()
display.set('status', 'Restarting wlan0 now!') display.set('status', 'Restarting wlan0 now!')
@ -171,7 +189,7 @@ class FixServices(plugins.Plugin):
elif level == "debug": elif level == "debug":
logging.debug(message) logging.debug(message)
else: else:
logging.info(message) logging.debug(message)
if ui: if ui:
ui.update(force=force, new_data=displayData) ui.update(force=force, new_data=displayData)
@ -186,7 +204,7 @@ class FixServices(plugins.Plugin):
# avoid overlapping restarts, but allow it if it's been a while # avoid overlapping restarts, but allow it if it's been a while
# (in case the last attempt failed before resetting "isReloadingMon") # (in case the last attempt failed before resetting "isReloadingMon")
if self.isReloadingMon and (time.time() - self.LASTTRY) < 180: if self.isReloadingMon and (time.time() - self.LASTTRY) < 180:
logging.info("[Fix_Services] Duplicate attempt ignored") logging.debug("[Fix_Services] Duplicate attempt ignored")
else: else:
self.isReloadingMon = True self.isReloadingMon = True
self.LASTTRY = time.time() self.LASTTRY = time.time()
@ -213,7 +231,7 @@ class FixServices(plugins.Plugin):
cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True) cmd_output = subprocess.check_output("ip link show wlan0mon", shell=True)
logging.debug("[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): 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 # not reliable, so don't skip just yet
# print("wlan0mon is up. Skipping reset.") # print("wlan0mon is up. Skipping reset.")
# self.isReloadingMon = False # self.isReloadingMon = False
@ -234,7 +252,7 @@ class FixServices(plugins.Plugin):
except Exception as err: except Exception as err:
logging.error("[Fix_Services wifi.recon off] error %s" % (repr(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: try:
cmd_output = subprocess.check_output("monstop", shell=True) cmd_output = subprocess.check_output("monstop", shell=True)
@ -250,14 +268,13 @@ class FixServices(plugins.Plugin):
# #
# Future: while "not fixed yet": blah blah blah. if "max_attemts", then reboot like the old days # Future: while "not fixed yet": blah blah blah. if "max_attemts", then reboot like the old days
# #
tries = 0 tries = 1
while tries < 3: while tries < 3:
try: try:
# unload the module # unload the module
cmd_output = subprocess.check_output("sudo modprobe -r brcmfmac", shell=True) cmd_output = subprocess.check_output("sudo modprobe -r brcmfmac", shell=True)
self.logPrintView("info", "[Fix_Services] unloaded brcmfmac", display, self.logPrintView("info", "[Fix_Services] unloaded brcmfmac", display,
{"status": "Turning it off #%s" % tries, "face": faces.SMART}) {"status": "Turning it off #%s" % tries, "face": faces.SMART})
time.sleep(1 + tries)
# reload the module # reload the module
try: try:
@ -265,27 +282,25 @@ class FixServices(plugins.Plugin):
cmd_output = subprocess.check_output("sudo modprobe brcmfmac", shell=True) cmd_output = subprocess.check_output("sudo modprobe brcmfmac", shell=True)
self.logPrintView("info", "[Fix_Services] reloaded brcmfmac") 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 # success! now make the mon0
try: try:
cmd_output = subprocess.check_output("monstart", shell=True) 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)) % (tries, cmd_output))
time.sleep(tries + 5)
try: try:
# try accessing mon0 in bettercap # try accessing mon0 in bettercap
result = connection.run("set wifi.interface wlan0mon") result = connection.run("set wifi.interface wlan0mon")
if "success" in result: if "success" in result:
logging.info("[Fix_Services set wifi.interface wlan0mon] worked!") logging.debug("[Fix_Services set wifi.interface wlan0mon worked!")
self._count = self._count + 1
time.sleep(1)
# stop looping and get back to recon # stop looping and get back to recon
break break
else: else:
logging.debug("[Fix_Services set wifi.interfaceface wlan0mon] failed? %s" % repr(result)) logging.debug(
"[Fix_Services set wifi.interfaceface wlan0mon] failed? %s" % repr(result))
except Exception as err: except Exception as err:
logging.debug("[Fix_Services set wifi.interface wlan0mon] except: %s" % repr(err)) logging.debug(
"[Fix_Services set wifi.interface wlan0mon] except: %s" % repr(err))
except Exception as cerr: # except Exception as cerr: #
if not display: if not display:
print("failed loading wlan0mon attempt #%s: %s" % (tries, repr(cerr))) print("failed loading wlan0mon attempt #%s: %s" % (tries, repr(cerr)))
@ -303,11 +318,11 @@ class FixServices(plugins.Plugin):
tries = tries + 1 tries = tries + 1
if tries < 3: 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: if not display:
print(" wlan0mon didn't make it. trying again") print(" wlan0mon didn't make it. trying again")
else: 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() pwnagotchi.reboot()
# exited the loop, so hopefully it loaded # exited the loop, so hopefully it loaded
@ -317,14 +332,14 @@ class FixServices(plugins.Plugin):
"face": faces.INTENSE}) "face": faces.INTENSE})
else: else:
print("And back on again...") print("And back on again...")
logging.info("[Fix_Services] wlan0mon back up") logging.debug("[Fix_Services] wlan0mon back up")
else: else:
self.LASTTRY = time.time() self.LASTTRY = time.time()
time.sleep(8 + tries * 2) # give it a bit before restarting recon in bettercap time.sleep(8 + tries * 2) # give it a bit before restarting recon in bettercap
self.isReloadingMon = False self.isReloadingMon = False
logging.info("[Fix_Services] re-enable recon") logging.debug("[Fix_Services] re-enable recon")
try: try:
result = connection.run("wifi.clear; wifi.recon on") result = connection.run("wifi.clear; wifi.recon on")
@ -345,13 +360,24 @@ class FixServices(plugins.Plugin):
logging.error("[Fix_Services wifi.recon on] %s" % repr(err)) logging.error("[Fix_Services wifi.recon on] %s" % repr(err))
pwnagotchi.reboot() pwnagotchi.reboot()
def on_unload(self, ui): # called to setup the ui elements
def on_ui_setup(self, ui):
with ui._lock: with ui._lock:
try: # add custom UI elements
logging.info("[Fix_Services] unloaded") if "position" in self.options:
except Exception as err: pos = self.options['position'].split(',')
logging.error("[Fix_Services] unload err %s " % repr(err)) pos = [int(x.strip()) for x in pos]
pass 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 # run from command line to brute force a reload

View File

@ -1,179 +0,0 @@
import logging
import subprocess
import os
import json
import pwnagotchi.plugins as plugins
from threading import Lock
'''
hcxpcapngtool needed, to install:
> git clone https://github.com/ZerBea/hcxtools.git
> cd hcxtools
> apt-get install libcurl4-openssl-dev libssl-dev zlib1g-dev
> make
> sudo make install
'''
class Hashie(plugins.Plugin):
__author__ = 'Jayofelony'
__version__ = '1.0.4'
__license__ = 'GPL3'
__description__ = '''
Attempt to automatically convert pcaps to a crackable format.
If successful, the files containing the hashes will be saved
in the same folder as the handshakes.
The files are saved in their respective Hashcat format:
- EAPOL hashes are saved as *.22000
- PMKID hashes are saved as *.16800
All PCAP files without enough information to create a hash are
stored in a file that can be read by the webgpsmap plugin.
Why use it?:
- Automatically convert handshakes to crackable formats!
We dont all upload our hashes online ;)
- Repair PMKID handshakes that hcxpcapngtool misses
- If running at time of handshake capture, on_handshake can
be used to improve the chance of the repair succeeding
- Be a completionist! Not enough packets captured to crack a network?
This generates an output file for the webgpsmap plugin, use the
location data to revisit networks you need more packets for!
Additional information:
- Currently requires hcxpcapngtool compiled and installed
- Attempts to repair PMKID hashes when hcxpcapngtool cant find the SSID
- hcxpcapngtool sometimes has trouble extracting the SSID, so we
use the raw 16800 output and attempt to retrieve the SSID via tcpdump
- When access_point data is available (on_handshake), we leverage
the reported AP name and MAC to complete the hash
- The repair is very basic and could certainly be improved!
Todo:
Make it so users dont need hcxpcapngtool (unless it gets added to the base image)
Phase 1: Extract/construct 22000/16800 hashes through tcpdump commands
Phase 2: Extract/construct 22000/16800 hashes entirely in python
Improve the code, a lot
'''
def __init__(self):
self.lock = Lock()
self.options = dict()
def on_loaded(self):
logging.info("[Hashie] Plugin loaded")
def on_unloaded(self):
logging.info("[Hashie] Plugin unloaded")
# called when everything is ready and the main loop is about to start
def on_ready(self, agent):
config = agent.config()
handshake_dir = config['bettercap']['handshakes']
logging.info('[Hashie] Starting batch conversion of pcap files')
with self.lock:
self._process_stale_pcaps(handshake_dir)
def on_handshake(self, agent, filename, access_point, client_station):
with self.lock:
handshake_status = []
fullpathNoExt = filename.split('.')[0]
name = filename.split('/')[-1:][0].split('.')[0]
if os.path.isfile(fullpathNoExt + '.22000'):
handshake_status.append('Already have {}.22000 (EAPOL)'.format(name))
elif self._writeEAPOL(filename):
handshake_status.append('Created {}.22000 (EAPOL) from pcap'.format(name))
if os.path.isfile(fullpathNoExt + '.16800'):
handshake_status.append('Already have {}.16800 (PMKID)'.format(name))
elif self._writePMKID(filename):
handshake_status.append('Created {}.16800 (PMKID) from pcap'.format(name))
if handshake_status:
logging.info('[Hashie] Good news:\n\t' + '\n\t'.join(handshake_status))
def _writeEAPOL(self, fullpath):
fullpathNoExt = fullpath.split('.')[0]
filename = fullpath.split('/')[-1:][0].split('.')[0]
subprocess.getoutput('hcxpcapngtool -o {}.22000 {} >/dev/null 2>&1'.format(fullpathNoExt, fullpath))
if os.path.isfile(fullpathNoExt + '.22000'):
logging.debug('[Hashie] [+] EAPOL Success: {}.22000 created'.format(filename))
return True
return False
def _writePMKID(self, fullpath):
fullpathNoExt = fullpath.split('.')[0]
filename = fullpath.split('/')[-1:][0].split('.')[0]
subprocess.getoutput('hcxpcapngtool -o {}.16800 {} >/dev/null 2>&1'.format(fullpathNoExt, fullpath))
if os.path.isfile(fullpathNoExt + '.16800'):
logging.debug('[Hashie] [+] PMKID Success: {}.16800 created'.format(filename))
return True
return False
def _process_stale_pcaps(self, handshake_dir):
handshakes_list = [os.path.join(handshake_dir, filename) for filename in os.listdir(handshake_dir) if filename.endswith('.pcap')]
failed_jobs = []
successful_jobs = []
lonely_pcaps = []
for num, handshake in enumerate(handshakes_list):
fullpathNoExt = handshake.split('.')[0]
pcapFileName = handshake.split('/')[-1:][0]
if not os.path.isfile(fullpathNoExt + '.22000'): # if no 22000, try
if self._writeEAPOL(handshake):
successful_jobs.append('22000: ' + pcapFileName)
else:
failed_jobs.append('22000: ' + pcapFileName)
if not os.path.isfile(fullpathNoExt + '.16800'): # if no 16800, try
if self._writePMKID(handshake):
successful_jobs.append('16800: ' + pcapFileName)
else:
failed_jobs.append('16800: ' + pcapFileName)
if not os.path.isfile(fullpathNoExt + '.22000'): # if no 16800 AND no 22000
lonely_pcaps.append(handshake)
logging.debug('[hashie] Batch job: added {} to lonely list'.format(pcapFileName))
if ((num + 1) % 50 == 0) or (num + 1 == len(handshakes_list)): # report progress every 50, or when done
logging.info('[Hashie] Batch job: {}/{} done ({} fails)'.format(num + 1, len(handshakes_list), len(lonely_pcaps)))
if successful_jobs:
logging.info('[Hashie] Batch job: {} new handshake files created'.format(len(successful_jobs)))
if lonely_pcaps:
logging.info('[Hashie] Batch job: {} networks without enough packets to create a hash'.format(len(lonely_pcaps)))
self._getLocations(lonely_pcaps)
def _getLocations(self, lonely_pcaps):
# export a file for webgpsmap to load
with open('/root/.incompletePcaps', 'w') as isIncomplete:
count = 0
for pcapFile in lonely_pcaps:
filename = pcapFile.split('/')[-1:][0] # keep extension
fullpathNoExt = pcapFile.split('.')[0]
isIncomplete.write(filename + '\n')
if os.path.isfile(fullpathNoExt + '.gps.json') or os.path.isfile(fullpathNoExt + '.geo.json'):
count += 1
if count != 0:
logging.info('[Hashie] Used {} GPS/GEO files to find lonely networks, '
'go check webgpsmap! ;)'.format(str(count)))
else:
logging.info('[Hashie] Could not find any GPS/GEO files '
'for the lonely networks'.format(str(count)))
def _getLocationsCSV(self, lonely_pcaps):
# in case we need this later, export locations manually to CSV file, needs try/catch format/etc.
locations = []
for pcapFile in lonely_pcaps:
filename = pcapFile.split('/')[-1:][0].split('.')[0]
fullpathNoExt = pcapFile.split('.')[0]
if os.path.isfile(fullpathNoExt + '.gps.json'):
with open(fullpathNoExt + '.gps.json', 'r') as tempFileA:
data = json.load(tempFileA)
locations.append(filename + ',' + str(data['Latitude']) + ',' + str(data['Longitude']) + ',50')
elif os.path.isfile(fullpathNoExt + '.geo.json'):
with open(fullpathNoExt + '.geo.json', 'r') as tempFileB:
data = json.load(tempFileB)
locations.append(
filename + ',' + str(data['location']['lat']) + ',' + str(data['location']['lng']) + ',' + str(data['accuracy']))
if locations:
with open('/root/locations.csv', 'w') as tempFileD:
for loc in locations:
tempFileD.write(loc + '\n')
logging.info('[Hashie] Used {} GPS/GEO files to find lonely networks, '
'load /root/locations.csv into a mapping app and go say hi!'.format(len(locations)))

View File

@ -144,6 +144,9 @@ class MemTemp(plugins.Plugin):
elif ui.is_waveshare2in7(): elif ui.is_waveshare2in7():
h_pos = (192, 138) h_pos = (192, 138)
v_pos = (211, 122) v_pos = (211, 122)
elif ui.is_waveshare1in54V2():
h_pos = (53, 77)
v_pos = (154, 65)
else: else:
h_pos = (155, 76) h_pos = (155, 76)
v_pos = (175, 61) v_pos = (175, 61)

View File

@ -0,0 +1,42 @@
import logging
import requests
import pwnagotchi.plugins as plugins
'''
You need an bluetooth connection to your android phone which is running PAW server with the GPS "hack" from Systemik and edited by shaynemk
GUIDE HERE: https://community.pwnagotchi.ai/t/setting-up-paw-gps-on-android
'''
class PawGPS(plugins.Plugin):
__author__ = 'leont'
__version__ = '1.0.1'
__name__ = 'pawgps'
__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):
logging.info("[paw-gps] no IP Address defined in the config file, will uses paw server default (192.168.44.1:8080)")
def on_handshake(self, agent, filename, access_point, client_station):
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)):
ip = "192.168.44.1:8080"
else:
ip = self.options['ip']
try:
gps = requests.get('http://' + ip + '/gps.xhtml')
try:
gps_filename = filename.replace('.pcap', '.paw-gps.json')
logging.info("[paw-gps] saving GPS data to %s" % (gps_filename))
with open(gps_filename, 'w+t') as f:
f.write(gps.text)
except Exception as error:
logging.error(f"[paw-gps] encountered error while saving gps data: {error}")
except Exception as error:
logging.error(f"[paw-gps] encountered error while getting gps data: {error}")

View File

@ -1,4 +1,4 @@
from PIL import Image from PIL import Image, ImageOps
from textwrap import TextWrapper from textwrap import TextWrapper
@ -40,21 +40,37 @@ class FilledRect(Widget):
class Text(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) super().__init__(position, color)
self.value = value self.value = value
self.font = font self.font = font
self.wrap = wrap self.wrap = wrap
self.max_length = max_length self.max_length = max_length
self.wrapper = TextWrapper(width=self.max_length, replace_whitespace=False) if wrap else None self.wrapper = TextWrapper(width=self.max_length, replace_whitespace=False) if wrap else None
self.png = png
def draw(self, canvas, drawer): def draw(self, canvas, drawer):
if self.value is not None: if self.value is not None:
if self.wrap: if not self.png:
text = '\n'.join(self.wrapper.wrap(self.value)) 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: else:
text = self.value self.image = Image.open(self.value)
drawer.text(self.xy, text, font=self.font, fill=self.color) 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): class LabeledValue(Widget):

View File

@ -23,7 +23,9 @@ DEBUG = '(#__#)'
UPLOAD = '(1__0)' UPLOAD = '(1__0)'
UPLOAD1 = '(1__1)' UPLOAD1 = '(1__1)'
UPLOAD2 = '(0__1)' UPLOAD2 = '(0__1)'
PNG = False
POSITION_X = 0
POSITION_Y = 40
def load_from_config(config): def load_from_config(config):
for face_name, face_value in config.items(): for face_name, face_value in config.items():

View File

@ -12,6 +12,7 @@ from . import config
from . import LCD_1in44 from . import LCD_1in44
from PIL import ImageOps from PIL import ImageOps
class EPD(object): class EPD(object):
def __init__(self): def __init__(self):
self.width = 128 self.width = 128
@ -24,9 +25,8 @@ class EPD(object):
pass pass
def clear(self): def clear(self):
#self.LCD.LCD_Clear() self.LCD.LCD_Clear()
pass
def display(self, image): 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) self.LCD.LCD_ShowImage(rgb_im, 0, 0)

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

View File

@ -38,7 +38,8 @@ class Waveshare13in3k(DisplayImpl):
self._display.Clear() self._display.Clear()
def render(self, canvas): def render(self, canvas):
self._display.display(canvas) buf = self._display.getbuffer(canvas)
self._display.display(buf)
def clear(self): def clear(self):
self._display.Clear() self._display.Clear()

View File

@ -38,7 +38,8 @@ class Waveshare1in02(DisplayImpl):
self._display.Clear() self._display.Clear()
def render(self, canvas): def render(self, canvas):
self._display.display(canvas) buf = self._display.getbuffer(canvas)
self._display.display(buf)
def clear(self): def clear(self):
self._display.Clear() self._display.Clear()

View File

@ -34,12 +34,12 @@ class Waveshare154(DisplayImpl):
logging.info("initializing waveshare v1in54 display") logging.info("initializing waveshare v1in54 display")
from pwnagotchi.ui.hw.libs.waveshare.v1in54.epd1in54 import EPD from pwnagotchi.ui.hw.libs.waveshare.v1in54.epd1in54 import EPD
self._display = EPD() self._display = EPD()
self._display.init(0x00) self._display.init(self._display.lut_partial_update)
self._display.Clear() self._display.Clear()
def render(self, canvas): def render(self, canvas):
buf = self._display.getbuffer(canvas) buf = self._display.getbuffer(canvas)
self._display.display(buf, None) self._display.display(buf)
def clear(self): def clear(self):
# pass # pass

View File

@ -31,15 +31,27 @@ class Waveshare154V2(DisplayImpl):
return self._layout return self._layout
def initialize(self): def initialize(self):
logging.info("initializing waveshare v1in54_v2 display") logging.info("initializing waveshare1in54_v2 display")
from pwnagotchi.ui.hw.libs.waveshare.v1in54_v2.epd1in54_V2 import EPD from pwnagotchi.ui.hw.libs.waveshare.v1in54_v2.epd1in54_V2 import EPD
self._display = EPD() try:
self._display.init(False) # Double initialization is a workaround for the display not working after a reboot, or mirrored/flipped screen
self._display.Clear() self._display = EPD()
self._display.init(0)
self.clear()
self._display.init(1)
self.clear()
except Exception as e:
logging.error(f"failed to initialize waveshare1in54_v2 display: {e}")
def render(self, canvas): def render(self, canvas):
buf = self._display.getbuffer(canvas) try:
self._display.display(buf, None) buf = self._display.getbuffer(canvas)
self._display.displayPart(buf)
except Exception as e:
logging.error(f"failed to render to waveshare1in54_v2 display: {e}")
def clear(self): def clear(self):
self._display.Clear() try:
self._display.Clear(0xFF)
except Exception as e:
logging.error(f"failed to clear waveshare1in54_v2 display: {e}")

View File

@ -38,7 +38,8 @@ class Waveshare1in54c(DisplayImpl):
self._display.Clear() self._display.Clear()
def render(self, canvas): def render(self, canvas):
self._display.display(canvas) buf = self._display.getbuffer(canvas)
self._display.display(buf, None)
def clear(self): def clear(self):
self._display.Clear() self._display.Clear()

View File

@ -38,7 +38,8 @@ class Waveshare1in64g(DisplayImpl):
self._display.Clear() self._display.Clear()
def render(self, canvas): def render(self, canvas):
self._display.display(canvas) buf = self._display.getbuffer(canvas)
self._display.display(buf)
def clear(self): def clear(self):
self._display.Clear() self._display.Clear()

View File

@ -9,81 +9,38 @@ class WaveshareV1(DisplayImpl):
super(WaveshareV1, self).__init__(config, 'waveshare_1') super(WaveshareV1, self).__init__(config, 'waveshare_1')
def layout(self): def layout(self):
if self.config['color'] == 'black': fonts.setup(10, 8, 10, 35, 25, 9)
fonts.setup(10, 9, 10, 35, 25, 9) self._layout['width'] = 250
self._layout['width'] = 250 self._layout['height'] = 122
self._layout['height'] = 122 self._layout['face'] = (0, 40)
self._layout['face'] = (0, 40) self._layout['name'] = (5, 20)
self._layout['name'] = (5, 20) self._layout['channel'] = (0, 0)
self._layout['channel'] = (0, 0) self._layout['aps'] = (28, 0)
self._layout['aps'] = (28, 0) self._layout['uptime'] = (185, 0)
self._layout['uptime'] = (185, 0) self._layout['line1'] = [0, 14, 250, 14]
self._layout['line1'] = [0, 14, 250, 14] self._layout['line2'] = [0, 108, 250, 108]
self._layout['line2'] = [0, 108, 250, 108] self._layout['friend_face'] = (0, 92)
self._layout['friend_face'] = (0, 92) self._layout['friend_name'] = (40, 94)
self._layout['friend_name'] = (40, 94) self._layout['shakes'] = (0, 109)
self._layout['shakes'] = (0, 109) self._layout['mode'] = (225, 109)
self._layout['mode'] = (225, 109) self._layout['status'] = {
self._layout['status'] = { 'pos': (125, 20),
'pos': (125, 20), 'font': fonts.status_font(fonts.Medium),
'font': fonts.status_font(fonts.Medium), 'max': 20
'max': 20 }
}
else:
fonts.setup(10, 8, 10, 25, 25, 9)
self._layout['width'] = 212
self._layout['height'] = 104
self._layout['face'] = (0, 26)
self._layout['name'] = (5, 15)
self._layout['channel'] = (0, 0)
self._layout['aps'] = (28, 0)
self._layout['uptime'] = (147, 0)
self._layout['line1'] = [0, 12, 212, 12]
self._layout['line2'] = [0, 92, 212, 92]
self._layout['friend_face'] = (0, 76)
self._layout['friend_name'] = (40, 78)
self._layout['shakes'] = (0, 93)
self._layout['mode'] = (187, 93)
self._layout['status'] = {
'pos': (91, 15),
'font': fonts.status_font(fonts.Medium),
'max': 20
}
return self._layout return self._layout
def initialize(self): def initialize(self):
if self.config['color'] == 'black': logging.info("initializing waveshare v2in13_V1 display in monochromatic mode")
logging.info("initializing waveshare v2in13_V1 display in monochromatic mode") from pwnagotchi.ui.hw.libs.waveshare.v2in13_V1.epd2in13 import EPD
from pwnagotchi.ui.hw.libs.waveshare.v2in13_V1.epd2in13 import EPD self._display = EPD()
self._display = EPD() self._display.init(self._display.lut_full_update)
self._display.init(self._display.lut_full_update) self._display.Clear(0xFF)
self._display.Clear(0xFF) self._display.init(self._display.lut_partial_update)
self._display.init(self._display.lut_partial_update)
elif self.config['color'] == 'fastAndFurious':
logging.info("initializing waveshare v2in13_V1 3-color display in FAST MODE")
logging.info("THIS MAY BE POTENTIALLY DANGEROUS. NO WARRANTY IS PROVIDED")
logging.info("USE THIS DISPLAY IN THIS MODE AT YOUR OWN RISK")
from pwnagotchi.ui.hw.libs.waveshare.v2in13_V1.epd2in13bcFAST import EPD
self._display = EPD()
self._display.init()
self._display.Clear()
else:
logging.info("initializing waveshare v2in13_V1 display 3-color mode")
from pwnagotchi.ui.hw.libs.waveshare.v2in13_V1.epd2in13bc import EPD
self._display = EPD()
self._display.init()
self._display.Clear()
def render(self, canvas): def render(self, canvas):
if self.config['color'] == 'black': buf = self._display.getbuffer(canvas)
buf = self._display.getbuffer(canvas) self._display.display(buf)
self._display.display(buf)
elif self.config['color'] == 'fastAndFurious':
buf_black = self._display.getbuffer(canvas)
self._display.DisplayPartial(buf_black)
else:
buf_black = self._display.getbuffer(canvas)
self._display.displayBlack(buf_black)
def clear(self): def clear(self):
self._display.Clear(0xff) self._display.Clear(0xff)

View File

@ -9,47 +9,25 @@ class WaveshareV2(DisplayImpl):
super(WaveshareV2, self).__init__(config, 'waveshare_2') super(WaveshareV2, self).__init__(config, 'waveshare_2')
def layout(self): def layout(self):
if self.config['color'] == 'black': fonts.setup(10, 8, 10, 35, 25, 9)
fonts.setup(10, 9, 10, 35, 25, 9) self._layout['width'] = 250
self._layout['width'] = 250 self._layout['height'] = 122
self._layout['height'] = 122 self._layout['face'] = (0, 40)
self._layout['face'] = (0, 40) self._layout['name'] = (5, 20)
self._layout['name'] = (5, 20) self._layout['channel'] = (0, 0)
self._layout['channel'] = (0, 0) self._layout['aps'] = (28, 0)
self._layout['aps'] = (28, 0) self._layout['uptime'] = (185, 0)
self._layout['uptime'] = (185, 0) self._layout['line1'] = [0, 14, 250, 14]
self._layout['line1'] = [0, 14, 250, 14] self._layout['line2'] = [0, 108, 250, 108]
self._layout['line2'] = [0, 108, 250, 108] self._layout['friend_face'] = (0, 92)
self._layout['friend_face'] = (0, 92) self._layout['friend_name'] = (40, 94)
self._layout['friend_name'] = (40, 94) self._layout['shakes'] = (0, 109)
self._layout['shakes'] = (0, 109) self._layout['mode'] = (225, 109)
self._layout['mode'] = (225, 109) self._layout['status'] = {
self._layout['status'] = { 'pos': (125, 20),
'pos': (125, 20), 'font': fonts.status_font(fonts.Medium),
'font': fonts.status_font(fonts.Medium), 'max': 20
'max': 20 }
}
else:
fonts.setup(10, 8, 10, 25, 25, 9)
self._layout['width'] = 212
self._layout['height'] = 104
self._layout['face'] = (0, 26)
self._layout['name'] = (5, 15)
self._layout['channel'] = (0, 0)
self._layout['aps'] = (28, 0)
self._layout['status'] = (91, 15)
self._layout['uptime'] = (147, 0)
self._layout['line1'] = [0, 12, 212, 12]
self._layout['line2'] = [0, 92, 212, 92]
self._layout['friend_face'] = (0, 76)
self._layout['friend_name'] = (40, 78)
self._layout['shakes'] = (0, 93)
self._layout['mode'] = (187, 93)
self._layout['status'] = {
'pos': (125, 20),
'font': fonts.status_font(fonts.Medium),
'max': 14
}
return self._layout return self._layout
def initialize(self): def initialize(self):

View File

@ -10,47 +10,25 @@ class Waveshare2in13bV3(DisplayImpl):
super(Waveshare2in13bV3, self).__init__(config, 'waveshare2in13b_v3') super(Waveshare2in13bV3, self).__init__(config, 'waveshare2in13b_v3')
def layout(self): def layout(self):
if self.config['color'] == 'black': fonts.setup(10, 9, 10, 35, 25, 9)
fonts.setup(10, 9, 10, 35, 25, 9) self._layout['width'] = 250
self._layout['width'] = 250 self._layout['height'] = 122
self._layout['height'] = 122 self._layout['face'] = (0, 40)
self._layout['face'] = (0, 40) self._layout['name'] = (5, 20)
self._layout['name'] = (5, 20) self._layout['channel'] = (0, 0)
self._layout['channel'] = (0, 0) self._layout['aps'] = (28, 0)
self._layout['aps'] = (28, 0) self._layout['uptime'] = (185, 0)
self._layout['uptime'] = (185, 0) self._layout['line1'] = [0, 14, 250, 14]
self._layout['line1'] = [0, 14, 250, 14] self._layout['line2'] = [0, 108, 250, 108]
self._layout['line2'] = [0, 108, 250, 108] self._layout['friend_face'] = (0, 92)
self._layout['friend_face'] = (0, 92) self._layout['friend_name'] = (40, 94)
self._layout['friend_name'] = (40, 94) self._layout['shakes'] = (0, 109)
self._layout['shakes'] = (0, 109) self._layout['mode'] = (225, 109)
self._layout['mode'] = (225, 109) self._layout['status'] = {
self._layout['status'] = { 'pos': (125, 20),
'pos': (125, 20), 'font': fonts.status_font(fonts.Medium),
'font': fonts.status_font(fonts.Medium), 'max': 20
'max': 20 }
}
else:
fonts.setup(10, 8, 10, 25, 25, 9)
self._layout['width'] = 212
self._layout['height'] = 104
self._layout['face'] = (0, 26)
self._layout['name'] = (5, 15)
self._layout['channel'] = (0, 0)
self._layout['aps'] = (28, 0)
self._layout['status'] = (91, 15)
self._layout['uptime'] = (147, 0)
self._layout['line1'] = [0, 12, 212, 12]
self._layout['line2'] = [0, 92, 212, 92]
self._layout['friend_face'] = (0, 76)
self._layout['friend_name'] = (40, 78)
self._layout['shakes'] = (0, 93)
self._layout['mode'] = (187, 93)
self._layout['status'] = {
'pos': (125, 20),
'font': fonts.status_font(fonts.Medium),
'max': 14
}
return self._layout return self._layout
def initialize(self): def initialize(self):

View File

@ -10,47 +10,25 @@ class Waveshare213bV4(DisplayImpl):
super(Waveshare213bV4, self).__init__(config, 'waveshare2in13b_v4') super(Waveshare213bV4, self).__init__(config, 'waveshare2in13b_v4')
def layout(self): def layout(self):
if self.config['color'] == 'black': fonts.setup(10, 9, 10, 35, 25, 9)
fonts.setup(10, 9, 10, 35, 25, 9) self._layout['width'] = 250
self._layout['width'] = 250 self._layout['height'] = 122
self._layout['height'] = 122 self._layout['face'] = (0, 40)
self._layout['face'] = (0, 40) self._layout['name'] = (5, 20)
self._layout['name'] = (5, 20) self._layout['channel'] = (0, 0)
self._layout['channel'] = (0, 0) self._layout['aps'] = (28, 0)
self._layout['aps'] = (28, 0) self._layout['uptime'] = (185, 0)
self._layout['uptime'] = (185, 0) self._layout['line1'] = [0, 14, 250, 14]
self._layout['line1'] = [0, 14, 250, 14] self._layout['line2'] = [0, 108, 250, 108]
self._layout['line2'] = [0, 108, 250, 108] self._layout['friend_face'] = (0, 92)
self._layout['friend_face'] = (0, 92) self._layout['friend_name'] = (40, 94)
self._layout['friend_name'] = (40, 94) self._layout['shakes'] = (0, 109)
self._layout['shakes'] = (0, 109) self._layout['mode'] = (225, 109)
self._layout['mode'] = (225, 109) self._layout['status'] = {
self._layout['status'] = { 'pos': (125, 20),
'pos': (125, 20), 'font': fonts.status_font(fonts.Medium),
'font': fonts.status_font(fonts.Medium), 'max': 20
'max': 20 }
}
else:
fonts.setup(10, 8, 10, 25, 25, 9)
self._layout['width'] = 212
self._layout['height'] = 104
self._layout['face'] = (0, 26)
self._layout['name'] = (5, 15)
self._layout['channel'] = (0, 0)
self._layout['aps'] = (28, 0)
self._layout['status'] = (91, 15)
self._layout['uptime'] = (147, 0)
self._layout['line1'] = [0, 12, 212, 12]
self._layout['line2'] = [0, 92, 212, 92]
self._layout['friend_face'] = (0, 76)
self._layout['friend_name'] = (40, 78)
self._layout['shakes'] = (0, 93)
self._layout['mode'] = (187, 93)
self._layout['status'] = {
'pos': (125, 20),
'font': fonts.status_font(fonts.Medium),
'max': 14
}
return self._layout return self._layout
def initialize(self): def initialize(self):

View File

View File

@ -38,7 +38,8 @@ class Waveshare2in36g(DisplayImpl):
self._display.Clear() self._display.Clear()
def render(self, canvas): def render(self, canvas):
self._display.display(canvas) buf = self._display.getbuffer(canvas)
self._display.display(buf)
def clear(self): def clear(self):
self._display.Clear() self._display.Clear()

View File

@ -34,11 +34,12 @@ class Waveshare2in66(DisplayImpl):
logging.info("initializing waveshare 2.66 inch lcd display") logging.info("initializing waveshare 2.66 inch lcd display")
from pwnagotchi.ui.hw.libs.waveshare.v2in66.epd2in66 import EPD from pwnagotchi.ui.hw.libs.waveshare.v2in66.epd2in66 import EPD
self._display = EPD() self._display = EPD()
self._display.init(0) self._display.init(1)
self._display.Clear() self._display.Clear()
def render(self, canvas): def render(self, canvas):
self._display.display(canvas) buf = self._display.getbuffer(canvas)
self._display.display(buf)
def clear(self): def clear(self):
self._display.Clear() self._display.Clear()

View File

@ -38,7 +38,8 @@ class Waveshare2in66g(DisplayImpl):
self._display.Clear() self._display.Clear()
def render(self, canvas): def render(self, canvas):
self._display.display(canvas) buf = self._display.getbuffer(canvas)
self._display.display(buf)
def clear(self): def clear(self):
self._display.Clear() self._display.Clear()

View File

@ -35,13 +35,11 @@ class Waveshare27inchV2(DisplayImpl):
from pwnagotchi.ui.hw.libs.waveshare.v2in7_v2.epd2in7_V2 import EPD from pwnagotchi.ui.hw.libs.waveshare.v2in7_v2.epd2in7_V2 import EPD
self._display = EPD() self._display = EPD()
self._display.init() self._display.init()
# this must have changed by waveshare
# remove the 0xFF(Clear(0xFF)) other wise it errors. can't pass oxff and self
self._display.Clear() self._display.Clear()
def render(self, canvas): def render(self, canvas):
buf = self._display.getbuffer(canvas) buf = self._display.getbuffer(canvas)
self._display.display_Partial(buf, 0, 0, 176, 264) self._display.display_Fast(buf)
def clear(self): def clear(self):
# This line also removes the 0xFF # This line also removes the 0xFF

View File

@ -39,7 +39,7 @@ class Waveshare27b(DisplayImpl):
def render(self, canvas): def render(self, canvas):
buf = self._display.getbuffer(canvas) buf = self._display.getbuffer(canvas)
self._display.display(buf) self._display.display(buf, None)
def clear(self): def clear(self):
self._display.Clear(0xff) self._display.Clear(0xff)

View File

@ -39,7 +39,7 @@ class Waveshare27bV2(DisplayImpl):
def render(self, canvas): def render(self, canvas):
buf = self._display.getbuffer(canvas) buf = self._display.getbuffer(canvas)
self._display.display(buf) self._display.display(buf, None)
def clear(self): def clear(self):
self._display.Clear(0xff) self._display.Clear(0xff)

View File

@ -35,12 +35,12 @@ class Waveshare29bV3(DisplayImpl):
from pwnagotchi.ui.hw.libs.waveshare.v2in9b_v3.epd2in9b_V3 import EPD from pwnagotchi.ui.hw.libs.waveshare.v2in9b_v3.epd2in9b_V3 import EPD
self._display = EPD() self._display = EPD()
self._display.init() self._display.init()
self._display.Clear(0xFF) self._display.Clear()
self._display.init() self._display.init()
def render(self, canvas): def render(self, canvas):
buf = self._display.getbuffer(canvas) buf = self._display.getbuffer(canvas)
self._display.display(buf) self._display.display(buf, None)
def clear(self): def clear(self):
self._display.Clear(0xFF) self._display.Clear()

View File

@ -35,12 +35,12 @@ class Waveshare29bV4(DisplayImpl):
from pwnagotchi.ui.hw.libs.waveshare.v2in9b_v4.epd2in9b_V4 import EPD from pwnagotchi.ui.hw.libs.waveshare.v2in9b_v4.epd2in9b_V4 import EPD
self._display = EPD() self._display = EPD()
self._display.init() self._display.init()
self._display.Clear(0xFF) self._display.Clear()
self._display.init() self._display.init()
def render(self, canvas): def render(self, canvas):
buf = self._display.getbuffer(canvas) buf = self._display.getbuffer(canvas)
self._display.display(buf) self._display.display(buf, None)
def clear(self): def clear(self):
self._display.Clear(0xFF) self._display.Clear()

View File

@ -38,7 +38,8 @@ class Waveshare2in9bc(DisplayImpl):
self._display.Clear() self._display.Clear()
def render(self, canvas): def render(self, canvas):
self._display.display(canvas) buf = self._display.getbuffer(canvas)
self._display.display(buf, None)
def clear(self): def clear(self):
self._display.Clear() self._display.Clear()

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